diff --git a/package.json b/package.json index 292583720..3afc0cbe1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.0.0-alpha.124", + "version": "1.0.0-alpha.125", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/language/package.json b/packages/language/package.json index 5deb9a13f..54d50cdc0 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.0.0-alpha.124", + "version": "1.0.0-alpha.125", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/next/package.json b/packages/next/package.json index 0595299cd..564231ef8 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/next", - "version": "1.0.0-alpha.124", + "version": "1.0.0-alpha.125", "displayName": "ZenStack Next.js integration", "description": "ZenStack Next.js integration", "homepage": "https://zenstack.dev", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index f1f9dc929..2b4dafde4 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.124", + "version": "1.0.0-alpha.125", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/react/package.json b/packages/plugins/react/package.json index 7e6978a84..5c8dd0d0a 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.124", + "version": "1.0.0-alpha.125", "description": "ZenStack plugin and runtime for ReactJS", "main": "index.js", "repository": { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index 229b82512..693e39ac2 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.0.0-alpha.124", + "version": "1.0.0-alpha.125", "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 5c6159071..27157ec3c 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.0.0-alpha.124", + "version": "1.0.0-alpha.125", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index bf6bc0bb2..f92deefb3 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.124", + "version": "1.0.0-alpha.125", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 148954304..fd6615b61 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.124", + "version": "1.0.0-alpha.125", "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 a0bed4858..38651d07c 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.124", + "version": "1.0.0-alpha.125", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/cli/cli-util.ts b/packages/schema/src/cli/cli-util.ts index 0540ad9ba..c7b0d46b7 100644 --- a/packages/schema/src/cli/cli-util.ts +++ b/packages/schema/src/cli/cli-util.ts @@ -3,7 +3,7 @@ import { getLiteral, PluginError } from '@zenstackhq/sdk'; import colors from 'colors'; import fs from 'fs'; import getLatestVersion from 'get-latest-version'; -import { getDocument, LangiumDocument, LangiumDocuments } from 'langium'; +import { AstNode, getDocument, LangiumDocument, LangiumDocuments, Mutable } from 'langium'; import { NodeFileSystem } from 'langium/node'; import ora from 'ora'; import path from 'path'; @@ -193,7 +193,17 @@ export function eagerLoadAllImports( export function mergeImportsDeclarations(documents: LangiumDocuments, model: Model) { const importedModels = resolveTransitiveImports(documents, model); - model.declarations.push(...importedModels.flatMap((m) => m.declarations)); + + const importedDeclarations = importedModels.flatMap((m) => m.declarations); + + importedDeclarations.forEach((d) => { + const mutable = d as Mutable; + // The plugin might use $container to access the model + // need to make sure it is always resolved to the main model + mutable.$container = model; + }); + + model.declarations.push(...importedDeclarations); } export async function getPluginDocuments(services: ZModelServices, fileName: string): Promise { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 0d4bea15b..0087703b6 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.0.0-alpha.124", + "version": "1.0.0-alpha.125", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 725b3289f..5e1d26147 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.0.0-alpha.124", + "version": "1.0.0-alpha.125", "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 0118f2cd7..d1cd7af46 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.0.0-alpha.124", + "version": "1.0.0-alpha.125", "description": "ZenStack Test Tools", "main": "index.js", "publishConfig": { diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 5ee96fe4e..0431cac1a 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -11,6 +11,18 @@ import tmp from 'tmp'; import { loadDocument } from 'zenstack/cli/cli-util'; import prismaPlugin from 'zenstack/plugins/prisma'; +/** + * Use it to represent multiple files in a single string like this + `schema.zmodel + import "user" + ${FILE_SPLITTER}user.zmodel + import "schema" + model User { + ... + }` +*/ +export const FILE_SPLITTER = '#FILE_SPLITTER#'; + export type WeakDbOperations = { [key in keyof DbOperations]: (...args: any[]) => Promise; }; @@ -112,17 +124,42 @@ export async function loadSchema( console.log('Workdir:', projectRoot); process.chdir(projectRoot); - schema = schema.replaceAll('$projectRoot', projectRoot); - let zmodelPath = path.join(projectRoot, 'schema.zmodel'); - const content = addPrelude ? `${MODEL_PRELUDE}\n${schema}` : schema; - if (customSchemaFilePath) { - zmodelPath = path.join(projectRoot, customSchemaFilePath); - fs.mkdirSync(path.dirname(zmodelPath), { recursive: true }); - fs.writeFileSync(zmodelPath, content); + + const files = schema.split(FILE_SPLITTER); + + if (files.length > 1) { + // multiple files + files.forEach((file, index) => { + //first line is the file name + const firstLine = file.indexOf('\n'); + const fileName = file.substring(0, firstLine).trim(); + let fileContent = file.substring(firstLine + 1); + if (index === 0) { + // The first file is the main schema file + zmodelPath = path.join(projectRoot, fileName); + if (addPrelude) { + // plugin need to be added after import statement + fileContent = `${fileContent}\n${MODEL_PRELUDE}`; + } + } + + fileContent = fileContent.replaceAll('$projectRoot', projectRoot); + const filePath = path.join(projectRoot, fileName); + fs.writeFileSync(filePath, fileContent); + }); } else { - fs.writeFileSync('schema.zmodel', content); + schema = schema.replaceAll('$projectRoot', projectRoot); + const content = addPrelude ? `${MODEL_PRELUDE}\n${schema}` : schema; + if (customSchemaFilePath) { + zmodelPath = path.join(projectRoot, customSchemaFilePath); + fs.mkdirSync(path.dirname(zmodelPath), { recursive: true }); + fs.writeFileSync(zmodelPath, content); + } else { + fs.writeFileSync('schema.zmodel', content); + } } + run('npm install'); if (customSchemaFilePath) { diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index 2f1594ce2..816bd6586 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -126,7 +126,7 @@ }, "../../../packages/runtime/dist": { "name": "@zenstackhq/runtime", - "version": "1.0.0-alpha.124", + "version": "1.0.0-alpha.125", "license": "MIT", "dependencies": { "@paralleldrive/cuid2": "^2.2.0", @@ -159,7 +159,7 @@ }, "../../../packages/schema/dist": { "name": "zenstack", - "version": "1.0.0-alpha.124", + "version": "1.0.0-alpha.125", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/tests/integration/tests/with-policy/multi-file.test.ts b/tests/integration/tests/with-policy/multi-file.test.ts new file mode 100644 index 000000000..ade561b51 --- /dev/null +++ b/tests/integration/tests/with-policy/multi-file.test.ts @@ -0,0 +1,32 @@ +import { loadSchema, FILE_SPLITTER } from '@zenstackhq/testtools'; + +describe('Multiple file test', () => { + it('model loading', async () => { + await loadSchema( + `schema.zmodel + import "post" + ${FILE_SPLITTER}post.zmodel + import "user" + model Post { + id Int @id @default(autoincrement()) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + // require login + @@deny('all', auth() == null) + + // can be read by owner or space members (only if not private) + @@allow('all', owner == auth()) + } + ${FILE_SPLITTER}user.zmodel + import "post" + model User { + id Int @id @default(autoincrement()) + name String + email String @unique + posts Post[] + } + ` + ); + }); +});