Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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])

/**
* 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])

/**
* 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
2 changes: 1 addition & 1 deletion packages/runtime/src/client/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const NUMERIC_FIELD_TYPES = ['Int', 'Float', 'BigInt', 'Decimal'];
/**
* Client API methods that are not supported in transactions.
*/
export const TRANSACTION_UNSUPPORTED_METHODS = ['$transaction', '$disconnect', '$use'] as const;
export const TRANSACTION_UNSUPPORTED_METHODS = ['$transaction', '$connect', '$disconnect', '$use'] as const;

/**
* Prefix for JSON field used to store joined delegate rows.
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>;

/**
* 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;
}

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);

return subQuery.as('$sub');
});
Expand Down
Loading