Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-v3",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"description": "ZenStack",
"packageManager": "pnpm@10.12.1",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack CLI",
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"type": "module",
"author": {
"name": "ZenStack Team"
Expand Down
2 changes: 1 addition & 1 deletion packages/common-helpers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/common-helpers",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"description": "ZenStack Common Helpers",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/config/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/eslint-config",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"type": "module",
"private": true,
"license": "MIT"
Expand Down
2 changes: 1 addition & 1 deletion packages/config/typescript-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/typescript-config",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"private": true,
"license": "MIT"
}
2 changes: 1 addition & 1 deletion packages/config/vitest-config/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/vitest-config",
"type": "module",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"private": true,
"license": "MIT",
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/create-zenstack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-zenstack",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"description": "Create a new ZenStack project",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/dialects/sql.js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/kysely-sql-js",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"description": "Kysely dialect for sql.js",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/vscode/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zenstack-v3",
"publisher": "zenstack",
"version": "3.0.9",
"version": "3.0.11",
"displayName": "ZenStack V3 Language Tools",
"description": "VSCode extension for ZenStack (v3) ZModel language",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/language/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/language",
"description": "ZenStack ZModel language specification",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"license": "MIT",
"author": "ZenStack Team",
"files": [
Expand Down
26 changes: 13 additions & 13 deletions packages/language/res/stdlib.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ function dbgenerated(expr: String?): Any {
function contains(field: String, search: String, caseInSensitive: Boolean?): Boolean {
} @@@expressionContext([AccessPolicy, ValidationRule])

/**
* If the field value matches the search condition with [full-text-search](https://www.prisma.io/docs/concepts/components/prisma-client/full-text-search). Need to enable "fullTextSearch" preview feature to use.
*/
function search(field: String, search: String): Boolean {
} @@@expressionContext([AccessPolicy])
// /**
// * If the field value matches the search condition with [full-text-search](https://www.prisma.io/docs/concepts/components/prisma-client/full-text-search). Need to enable "fullTextSearch" preview feature to use.
// */
// function search(field: String, search: String): Boolean {
// } @@@expressionContext([AccessPolicy])
Comment thread
ymc9 marked this conversation as resolved.

/**
* Checks the field value starts with the search string. By default, the search is case-sensitive, and
Expand All @@ -151,25 +151,25 @@ function endsWith(field: String, search: String, caseInSensitive: Boolean?): Boo
} @@@expressionContext([AccessPolicy, ValidationRule])

/**
* If the field value (a list) has the given search value
* Checks if the list field has the given search value
*/
function has(field: Any[], search: Any): Boolean {
} @@@expressionContext([AccessPolicy, ValidationRule])

/**
* If the field value (a list) has every element of the search list
* Checks if the list field has at least one element of the search list
*/
function hasEvery(field: Any[], search: Any[]): Boolean {
function hasSome(field: Any[], search: Any[]): Boolean {
} @@@expressionContext([AccessPolicy, ValidationRule])

/**
* If the field value (a list) has at least one element of the search list
* Checks if the list field has every element of the search list
*/
function hasSome(field: Any[], search: Any[]): Boolean {
function hasEvery(field: Any[], search: Any[]): Boolean {
} @@@expressionContext([AccessPolicy, ValidationRule])
Comment thread
ymc9 marked this conversation as resolved.

/**
* If the field value (a list) is empty
* Checks if the list field is empty
*/
function isEmpty(field: Any[]): Boolean {
} @@@expressionContext([AccessPolicy, ValidationRule])
Expand Down Expand Up @@ -551,9 +551,9 @@ function length(field: Any): Int {


/**
* Validates a string field value matches a regex.
* Validates a string field value matches a regex pattern.
*/
function regex(field: String, regex: String): Boolean {
function regex(field: String, pattern: String): Boolean {
} @@@expressionContext([ValidationRule])

/**
Expand Down
6 changes: 6 additions & 0 deletions packages/language/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,19 @@ export function createZModelLanguageServices(

// when documents reach Parsed state, inspect plugin declarations and load corresponding
// plugin zmodel docs
// Note we must use `onBuildPhase` instead of `onDocumentPhase` here because the latter is
// not called when not running inside a language server.
shared.workspace.DocumentBuilder.onBuildPhase(DocumentState.Parsed, async (documents) => {
for (const doc of documents) {
if (doc.parseResult.lexerErrors.length > 0 || doc.parseResult.parserErrors.length > 0) {
// balk if there are lexer or parser errors
continue;
}

if (doc.uri.scheme !== 'file') {
continue;
}

const schemaPath = fileURLToPath(doc.uri.toString());
const pluginSchemas = getPluginDocuments(doc.parseResult.value as Model, schemaPath);
for (const plugin of pluginSchemas) {
Expand Down
4 changes: 2 additions & 2 deletions packages/language/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,9 @@ export function getAllDeclarationsIncludingImports(documents: LangiumDocuments,
}

export function getAuthDecl(decls: (DataModel | TypeDef)[]) {
let authModel = decls.find((m) => hasAttribute(m, '@@auth'));
let authModel = decls.find((d) => hasAttribute(d, '@@auth'));
if (!authModel) {
authModel = decls.find((m) => m.name === 'User');
authModel = decls.find((d) => d.name === 'User');
}
return authModel;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,13 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
}
}

@check('@@unique')
@check('@@id')
@check('@@index')
@check('@@unique')
// @ts-expect-error
private _checkUnique(attr: AttributeApplication, accept: ValidationAcceptor) {
private _checkConstraint(attr: AttributeApplication, accept: ValidationAcceptor) {
const fields = attr.args[0]?.value;
const attrName = attr.decl.ref?.name;
if (!fields) {
accept('error', `expects an array of field references`, {
node: attr.args[0]!,
Expand All @@ -303,7 +305,7 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
}
if (isArrayExpr(fields)) {
if (fields.items.length === 0) {
accept('error', `\`@@unique\` expects at least one field reference`, { node: fields });
accept('error', `\`${attrName}\` expects at least one field reference`, { node: fields });
return;
}
fields.items.forEach((item) => {
Expand All @@ -321,7 +323,7 @@ export default class AttributeApplicationValidator implements AstValidator<Attri
}

if (item.target.ref.$container !== attr.$container && isDelegateModel(item.target.ref.$container)) {
accept('error', `Cannot use fields inherited from a polymorphic base model in \`@@unique\``, {
accept('error', `Cannot use fields inherited from a polymorphic base model in \`${attrName}\``, {
node: item,
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/policy/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/plugin-policy",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"description": "ZenStack Policy Plugin",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/runtime",
"version": "3.0.0-beta.10",
"version": "3.0.0-beta.11",
"description": "ZenStack Runtime",
"type": "module",
"scripts": {
Expand Down
6 changes: 6 additions & 0 deletions packages/runtime/src/client/client-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,12 @@ export class ClientImpl<Schema extends SchemaDef> {
return (procOptions[name] as Function).apply(this, [this, ...args]);
}

async $connect() {
await this.kysely.connection().execute(async (conn) => {
await conn.executeQuery(sql`select 1`.compile(this.kysely));
});
}

async $disconnect() {
await this.kysely.destroy();
}
Expand Down
7 changes: 6 additions & 1 deletion packages/runtime/src/client/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@ export type ClientContract<Schema extends SchemaDef> = {
$unuseAll(): ClientContract<Schema>;

/**
* Disconnects the underlying Kysely instance from the database.
* Eagerly connects to the database.
*/
$connect(): Promise<void>;

Comment thread
ymc9 marked this conversation as resolved.
/**
* Explicitly disconnects from the database.
*/
$disconnect(): Promise<void>;

Expand Down
56 changes: 30 additions & 26 deletions packages/runtime/src/client/crud/dialects/base-dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { InternalError, QueryError } from '../../errors';
import type { ClientOptions } from '../../options';
import {
aggregate,
buildFieldRef,
buildJoinPairs,
ensureArray,
flattenCompoundUniqueFilters,
Expand Down Expand Up @@ -89,14 +88,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
result = this.buildSkipTake(result, skip, take);

// orderBy
result = this.buildOrderBy(
result,
model,
modelAlias,
args.orderBy,
skip !== undefined || take !== undefined,
negateOrderBy,
);
result = this.buildOrderBy(result, model, modelAlias, args.orderBy, negateOrderBy);

// distinct
if ('distinct' in args && (args as any).distinct) {
Expand Down Expand Up @@ -748,15 +740,10 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
model: string,
modelAlias: string,
orderBy: OrArray<OrderBy<Schema, GetModels<Schema>, boolean, boolean>> | undefined,
useDefaultIfEmpty: boolean,
negated: boolean,
) {
if (!orderBy) {
if (useDefaultIfEmpty) {
orderBy = makeDefaultOrderBy(this.schema, model);
} else {
return query;
}
return query;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

let result = query;
Expand Down Expand Up @@ -862,7 +849,7 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
),
);
});
result = this.buildOrderBy(result, fieldDef.type, relationModel, value, false, negated);
result = this.buildOrderBy(result, fieldDef.type, relationModel, value, negated);
}
}
}
Expand Down Expand Up @@ -943,15 +930,13 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
field: string,
): SelectQueryBuilder<any, any, any> {
const fieldDef = requireField(this.schema, model, field);
if (fieldDef.computed) {
// TODO: computed field from delegate base?
return query.select(() => this.fieldRef(model, field, modelAlias).as(field));
} else if (!fieldDef.originModel) {
// regular field
return query.select(this.eb.ref(`${modelAlias}.${field}`).as(field));
} else {
return this.buildSelectField(query, fieldDef.originModel, fieldDef.originModel, field);
}

// if field is defined on a delegate base, the base model is joined with its
// model name from outer query, so we should use it directly as the alias
const fieldModel = fieldDef.originModel ?? model;
const alias = fieldDef.originModel ?? modelAlias;

return query.select(() => this.fieldRef(fieldModel, field, alias).as(field));
}

buildDelegateJoin(
Expand Down Expand Up @@ -1083,7 +1068,26 @@ export abstract class BaseCrudDialect<Schema extends SchemaDef> {
}

fieldRef(model: string, field: string, modelAlias?: string, inlineComputedField = true) {
return buildFieldRef(this.schema, model, field, this.options, this.eb, modelAlias, inlineComputedField);
const fieldDef = requireField(this.schema, model, field);

if (!fieldDef.computed) {
// regular field
return this.eb.ref(modelAlias ? `${modelAlias}.${field}` : field);
} else {
// computed field
if (!inlineComputedField) {
return this.eb.ref(modelAlias ? `${modelAlias}.${field}` : field);
}
let computer: Function | undefined;
if ('computedFields' in this.options) {
const computedFields = this.options.computedFields as Record<string, any>;
computer = computedFields?.[fieldDef.originModel ?? model]?.[field];
}
if (!computer) {
throw new QueryError(`Computed field "${field}" implementation not provided for model "${model}"`);
}
return computer(this.eb, { modelAlias });
}
}

protected canJoinWithoutNestedSelect(
Expand Down
9 changes: 1 addition & 8 deletions packages/runtime/src/client/crud/operations/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,7 @@ export class AggregateOperationHandler<Schema extends SchemaDef> extends BaseOpe
subQuery = this.dialect.buildSkipTake(subQuery, skip, take);

// orderBy
subQuery = this.dialect.buildOrderBy(
subQuery,
this.model,
this.model,
parsedArgs.orderBy,
skip !== undefined || take !== undefined,
negateOrderBy,
);
subQuery = this.dialect.buildOrderBy(subQuery, this.model, this.model, parsedArgs.orderBy, negateOrderBy);

Comment thread
ymc9 marked this conversation as resolved.
return subQuery.as('$sub');
});
Expand Down
Loading