diff --git a/packages/language/res/stdlib.zmodel b/packages/language/res/stdlib.zmodel index c248bde0..e43c389c 100644 --- a/packages/language/res/stdlib.zmodel +++ b/packages/language/res/stdlib.zmodel @@ -116,12 +116,6 @@ function autoincrement(): Int { function dbgenerated(expr: String?): Any { } @@@expressionContext([DefaultValue]) -/** - * Gets entities value before an update. Only valid when used in a "update" policy rule. - */ -function future(): Any { -} @@@expressionContext([AccessPolicy]) - /** * Checks if the field value contains the search string. By default, the search is case-sensitive, and * "LIKE" operator is used to match. If `caseInSensitive` is true, "ILIKE" operator is used if @@ -663,3 +657,56 @@ attribute @meta(_ name: String, _ value: Any) * Marks an attribute as deprecated. */ attribute @@@deprecated(_ message: String) + +/* --- Policy Plugin --- */ + +/** + * Defines an access policy that allows a set of operations when the given condition is true. + * + * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. + * @param condition: a boolean expression that controls if the operation should be allowed. + */ +attribute @@allow(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean) + +/** + * Defines an access policy that allows the annotated field to be read or updated. + * You can pass a third argument as `true` to make it override the model-level policies. + * + * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. + * @param condition: a boolean expression that controls if the operation should be allowed. + * @param override: a boolean value that controls if the field-level policy should override the model-level policy. + */ +attribute @allow(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean, _ override: Boolean?) + +/** + * Defines an access policy that denies a set of operations when the given condition is true. + * + * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. + * @param condition: a boolean expression that controls if the operation should be denied. + */ +attribute @@deny(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean) + +/** + * Defines an access policy that denies the annotated field to be read or updated. + * + * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. + * @param condition: a boolean expression that controls if the operation should be denied. + */ +attribute @deny(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean) + +/** + * Checks if the current user can perform the given operation on the given field. + * + * @param field: The field to check access for + * @param operation: The operation to check access for. Can be "read", "create", "update", or "delete". If the operation is not provided, + * it defaults the operation of the containing policy rule. + */ +function check(field: Any, operation: String?): Boolean { +} @@@expressionContext([AccessPolicy]) + +/** + * Gets entities value before an update. Only valid when used in a "update" policy rule. + */ +function future(): Any { +} @@@expressionContext([AccessPolicy]) + diff --git a/packages/language/test/utils.ts b/packages/language/test/utils.ts index b14bdabb..4b60ce42 100644 --- a/packages/language/test/utils.ts +++ b/packages/language/test/utils.ts @@ -1,5 +1,4 @@ import { invariant } from '@zenstackhq/common-helpers'; -import { glob } from 'glob'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; @@ -10,7 +9,7 @@ export async function loadSchema(schema: string) { // create a temp file const tempFile = path.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`); fs.writeFileSync(tempFile, schema); - const r = await loadDocument(tempFile, getPluginModels()); + const r = await loadDocument(tempFile); expect(r).toSatisfy( (r) => r.success, `Failed to load schema: ${(r as any).errors?.map((e) => e.toString()).join(', ')}`, @@ -23,7 +22,7 @@ export async function loadSchemaWithError(schema: string, error: string | RegExp // create a temp file const tempFile = path.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`); fs.writeFileSync(tempFile, schema); - const r = await loadDocument(tempFile, getPluginModels()); + const r = await loadDocument(tempFile); expect(r.success).toBe(false); invariant(!r.success); if (typeof error === 'string') { @@ -38,6 +37,3 @@ export async function loadSchemaWithError(schema: string, error: string | RegExp ); } } -function getPluginModels() { - return glob.sync(path.resolve(__dirname, '../../runtime/src/plugins/**/plugin.zmodel')); -} diff --git a/packages/runtime/src/plugins/policy/plugin.zmodel b/packages/runtime/src/plugins/policy/plugin.zmodel deleted file mode 100644 index 659705ce..00000000 --- a/packages/runtime/src/plugins/policy/plugin.zmodel +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Defines an access policy that allows a set of operations when the given condition is true. - * - * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. - * @param condition: a boolean expression that controls if the operation should be allowed. - */ -attribute @@allow(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean) - -/** - * Defines an access policy that allows the annotated field to be read or updated. - * You can pass a third argument as `true` to make it override the model-level policies. - * - * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. - * @param condition: a boolean expression that controls if the operation should be allowed. - * @param override: a boolean value that controls if the field-level policy should override the model-level policy. - */ -attribute @allow(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean, _ override: Boolean?) - -/** - * Defines an access policy that denies a set of operations when the given condition is true. - * - * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. - * @param condition: a boolean expression that controls if the operation should be denied. - */ -attribute @@deny(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean) - -/** - * Defines an access policy that denies the annotated field to be read or updated. - * - * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations. - * @param condition: a boolean expression that controls if the operation should be denied. - */ -attribute @deny(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean) - -/** - * Checks if the current user can perform the given operation on the given field. - * - * @param field: The field to check access for - * @param operation: The operation to check access for. Can be "read", "create", "update", or "delete". If the operation is not provided, - * it defaults the operation of the containing policy rule. - */ -function check(field: Any, operation: String?): Boolean { -} @@@expressionContext([AccessPolicy]) diff --git a/packages/runtime/test/scripts/generate.ts b/packages/runtime/test/scripts/generate.ts index 7e5f1293..df405295 100644 --- a/packages/runtime/test/scripts/generate.ts +++ b/packages/runtime/test/scripts/generate.ts @@ -19,8 +19,7 @@ async function generate(schemaPath: string) { const generator = new TsSchemaGenerator(); const outputDir = path.dirname(schemaPath); const tsPath = path.join(outputDir, 'schema.ts'); - const pluginModelFiles = glob.sync(path.resolve(dir, '../../dist/**/plugin.zmodel')); - const result = await loadDocument(schemaPath, pluginModelFiles); + const result = await loadDocument(schemaPath); if (!result.success) { throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`); } diff --git a/packages/runtime/test/utils.ts b/packages/runtime/test/utils.ts index b7245062..d5bea549 100644 --- a/packages/runtime/test/utils.ts +++ b/packages/runtime/test/utils.ts @@ -2,7 +2,7 @@ import { invariant } from '@zenstackhq/common-helpers'; import { loadDocument } from '@zenstackhq/language'; import type { Model } from '@zenstackhq/language/ast'; import { PrismaSchemaGenerator } from '@zenstackhq/sdk'; -import { createTestProject, generateTsSchema, getPluginModules } from '@zenstackhq/testtools'; +import { createTestProject, generateTsSchema } from '@zenstackhq/testtools'; import SQLite from 'better-sqlite3'; import { PostgresDialect, SqliteDialect, type LogEvent } from 'kysely'; import { execSync } from 'node:child_process'; @@ -113,7 +113,7 @@ export async function createTestClient( if (options?.usePrismaPush) { invariant(typeof schema === 'string' || schemaFile, 'a schema file must be provided when using prisma db push'); if (!model) { - const r = await loadDocument(path.join(workDir, 'schema.zmodel'), getPluginModules()); + const r = await loadDocument(path.join(workDir, 'schema.zmodel')); if (!r.success) { throw new Error(r.errors.join('\n')); } diff --git a/packages/runtime/tsup.config.ts b/packages/runtime/tsup.config.ts index 795f2b2b..7f7a9348 100644 --- a/packages/runtime/tsup.config.ts +++ b/packages/runtime/tsup.config.ts @@ -1,5 +1,4 @@ import { defineConfig } from 'tsup'; -import fs from 'node:fs'; export default defineConfig({ entry: { @@ -14,7 +13,4 @@ export default defineConfig({ clean: true, dts: true, format: ['cjs', 'esm'], - async onSuccess() { - fs.cpSync('src/plugins/policy/plugin.zmodel', 'dist/plugins/policy/plugin.zmodel'); - }, }); diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index b4f5386e..ee57a0c5 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -1,7 +1,6 @@ import { loadDocument } from '@zenstackhq/language'; import { TsSchemaGenerator } from '@zenstackhq/sdk'; import type { SchemaDef } from '@zenstackhq/sdk/schema'; -import { glob } from 'glob'; import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; @@ -41,8 +40,7 @@ export async function generateTsSchema( const noPrelude = schemaText.includes('datasource '); fs.writeFileSync(zmodelPath, `${noPrelude ? '' : makePrelude(provider, dbUrl)}\n\n${schemaText}`); - const pluginModelFiles = getPluginModules(); - const result = await loadDocument(zmodelPath, pluginModelFiles); + const result = await loadDocument(zmodelPath); if (!result.success) { throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`); } @@ -62,10 +60,6 @@ export async function generateTsSchema( return { ...(await compileAndLoad(workDir)), model: result.model }; } -export function getPluginModules() { - return glob.sync(path.resolve(__dirname, '../../runtime/src/plugins/**/plugin.zmodel')); -} - async function compileAndLoad(workDir: string) { execSync('npx tsc', { cwd: workDir, @@ -84,8 +78,7 @@ export function generateTsSchemaFromFile(filePath: string) { export async function generateTsSchemaInPlace(schemaPath: string) { const workDir = path.dirname(schemaPath); - const pluginModelFiles = glob.sync(path.resolve(__dirname, '../../runtime/src/plugins/**/plugin.zmodel')); - const result = await loadDocument(schemaPath, pluginModelFiles); + const result = await loadDocument(schemaPath); if (!result.success) { throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`); } diff --git a/tests/regression/generate.ts b/tests/regression/generate.ts index 86993b96..22959cda 100644 --- a/tests/regression/generate.ts +++ b/tests/regression/generate.ts @@ -18,9 +18,7 @@ async function main() { async function generate(schemaPath: string) { const generator = new TsSchemaGenerator(); const outputDir = path.dirname(schemaPath); - const tsPath = path.join(outputDir, 'schema.ts'); - const pluginModelFiles = glob.sync(path.resolve(dir, '../../packages/runtime/dist/**/plugin.zmodel')); - const result = await loadDocument(schemaPath, pluginModelFiles); + const result = await loadDocument(schemaPath); if (!result.success) { throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`); }