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 all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
233b47d
feat: plugin-extended query args (#599)
ymc9 Jan 16, 2026
33f6485
chore: add publish-canary command (#602)
ymc9 Jan 16, 2026
27e8910
feat: `between` operator (#569)
sanny-io Jan 16, 2026
2c1ffac
feat(orm): implement client API extensions, refactor query args exten…
ymc9 Jan 18, 2026
077f03f
feat(zmodel): collection predicate binding (#548)
mwillbanks Jan 18, 2026
94c5c8a
feat(cli): ZenStack proxy (#597)
jiashengguo Jan 18, 2026
9e819e8
add test for pg computed values across multiple schema (#615)
lsmith77 Jan 24, 2026
99f68e2
feat(orm): mysql support (#616)
ymc9 Jan 24, 2026
c7ad7d7
test: add basic memory stress test (#620)
ymc9 Jan 25, 2026
8fdd349
fix(better-auth): support custom table enum field types and defaults …
ymc9 Jan 26, 2026
84d1e60
perf(orm): more aggressive caching of validation zod schemas (#623)
ymc9 Jan 27, 2026
e8717e4
fix(cli): properly quote prisma exec path (#624)
ymc9 Jan 27, 2026
6719d4d
feat(cli): add mysql support to proxy (#625)
ymc9 Jan 27, 2026
209312a
fix(orm): properly handle literal array values in policy evaluation f…
ymc9 Jan 28, 2026
1d47af9
fix(orm): deal with node-pg timezone issue more reliably (#630)
ymc9 Jan 29, 2026
ab9535e
fix(language): resolve mixin fields from imported files in scope (#59…
ymc9 Jan 30, 2026
c5da349
feat(cli): add dotenv support (#629)
jiashengguo Jan 30, 2026
a42efc1
fix(cli): handle error for proxy server (#634)
jiashengguo Jan 30, 2026
2b3d7d6
feat: `ignore` argument for `@updatedAt` (#572)
sanny-io Jan 30, 2026
f9475e6
fix(cli): redact credentials from database URL console logs (#626)
Copilot Jan 30, 2026
3392104
chore: bump version 3.3.0 (#635)
github-actions[bot] Jan 30, 2026
895b8f1
Require explicit `ignore` argument for `@updatedAt` (#638)
sanny-io Jan 30, 2026
f612723
refactor(cli): replace dynamic function call eval with regex match fo…
jiashengguo Jan 30, 2026
14a0a03
fix handling of a single column unique index with externalIdMapping (…
lsmith77 Jan 30, 2026
b3aff66
fix(cli): update logLevel option to accept multiple values (#639)
jiashengguo Jan 30, 2026
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
19 changes: 18 additions & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,23 @@ jobs:
ports:
- 5432:5432

mysql:
image: mysql:8.4
env:
MYSQL_ROOT_PASSWORD: mysql
ports:
- 3306:3306
# Set health checks to wait until mysql has started
options: >-
--health-cmd="mysqladmin ping --silent"
--health-interval=10s
--health-timeout=5s
--health-retries=3

strategy:
matrix:
node-version: [22.x]
provider: [sqlite, postgresql]
provider: [sqlite, postgresql, mysql]

steps:
- name: Checkout
Expand Down Expand Up @@ -81,5 +94,9 @@ jobs:
- name: Lint
run: pnpm run lint

- name: Set MySQL max_connections
run: |
mysql -h 127.0.0.1 -uroot -pmysql -e "SET GLOBAL max_connections=500;"

- name: Test
run: TEST_DB_PROVIDER=${{ matrix.provider }} pnpm run test
5 changes: 2 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
- [ ] ZModel
- [x] Import
- [ ] View support
- [ ] Datasource provider-scoped attributes
- [ ] ORM
- [x] Create
- [x] Input validation
Expand Down Expand Up @@ -72,7 +71,7 @@
- [x] Query builder API
- [x] Computed fields
- [x] Plugin
- [ ] Custom procedures
- [x] Custom procedures
- [ ] Misc
- [x] JSDoc for CRUD methods
- [x] Cache validation schemas
Expand Down Expand Up @@ -110,4 +109,4 @@
- [x] SQLite
- [x] PostgreSQL
- [x] Multi-schema
- [ ] MySQL
- [x] MySQL
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-v3",
"version": "3.2.1",
"version": "3.3.0",
"description": "ZenStack",
"packageManager": "pnpm@10.23.0",
"type": "module",
Expand All @@ -9,8 +9,9 @@
"watch": "turbo run watch build",
"lint": "turbo run lint",
"test": "turbo run test",
"test:all": "pnpm run test:sqlite && pnpm run test:pg",
"test:all": "pnpm run test:sqlite && pnpm run test:pg && pnpm run test:mysql",
"test:pg": "TEST_DB_PROVIDER=postgresql turbo run test",
"test:mysql": "TEST_DB_PROVIDER=mysql turbo run test",
"test:sqlite": "TEST_DB_PROVIDER=sqlite turbo run test",
"test:coverage": "vitest run --coverage",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
Expand All @@ -19,6 +20,7 @@
"bump-patch": "gh workflow run .github/workflows/bump-version.yml --ref dev -f version_type=patch",
"bump-minor": "gh workflow run .github/workflows/bump-version.yml --ref dev -f version_type=minor",
"publish-all": "pnpm --filter \"./packages/**\" -r publish --access public",
"publish-canary": "pnpm --filter \"./packages/**\" -r publish --access public --tag canary --no-git-checks",
"publish-preview": "pnpm --filter \"./packages/**\" -r publish --force --registry https://preview.registry.zenstack.dev/",
"unpublish-preview": "pnpm --filter \"./packages/**\" -r --shell-mode exec -- npm unpublish -f --registry https://preview.registry.zenstack.dev/ \"\\$PNPM_PACKAGE_NAME\""
},
Expand Down
13 changes: 9 additions & 4 deletions packages/auth-adapters/better-auth/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "@zenstackhq/better-auth",
"version": "3.2.1",
"version": "3.3.0",
"description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.",
"type": "module",
"scripts": {
"build": "tsc --noEmit && tsup-node",
"watch": "tsup-node --watch",
"lint": "eslint src --ext ts",
"test": "vitest run",
"pack": "pnpm pack"
},
"keywords": [
Expand Down Expand Up @@ -45,10 +46,14 @@
"better-auth": "^1.3.0"
},
"devDependencies": {
"@better-auth/core": "^1.3.0",
"better-auth": "^1.3.0",
"@better-auth/core": "1.4.17",
"better-auth": "1.4.17",
"@better-auth/cli": "1.4.17",
"@types/tmp": "catalog:",
"@zenstackhq/cli": "workspace:*",
"@zenstackhq/eslint-config": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*"
"@zenstackhq/vitest-config": "workspace:*",
"tmp": "catalog:"
}
}
25 changes: 25 additions & 0 deletions packages/auth-adapters/better-auth/src/schema-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
InvocationExpr,
isDataModel,
Model,
NumberLiteral,
ReferenceExpr,
StringLiteral,
} from '@zenstackhq/language/ast';
Expand Down Expand Up @@ -259,6 +260,13 @@ function getMappedFieldType({ bigint, type }: DBFieldAttribute) {
.with('json', () => ({ type: 'Json' }))
.with('string[]', () => ({ type: 'String', array: true }))
.with('number[]', () => ({ type: 'Int', array: true }))
.when(
(v) => Array.isArray(v) && v.every((e) => typeof e === 'string'),
() => {
// Handle enum types (e.g., ['user', 'admin']), map them to String type for now
return { type: 'String' };
},
)
.otherwise(() => {
throw new Error(`Unsupported field type: ${type}`);
});
Expand Down Expand Up @@ -332,6 +340,10 @@ function addOrUpdateModel(
addDefaultNow(df);
} else if (typeof field.defaultValue === 'boolean') {
addFieldAttribute(df, '@default', [createBooleanAttributeArg(field.defaultValue)]);
} else if (typeof field.defaultValue === 'string') {
addFieldAttribute(df, '@default', [createStringAttributeArg(field.defaultValue)]);
} else if (typeof field.defaultValue === 'number') {
addFieldAttribute(df, '@default', [createNumberAttributeArg(field.defaultValue)]);
} else if (typeof field.defaultValue === 'function') {
// For other function-based defaults, we'll need to check what they return
const defaultVal = field.defaultValue();
Expand Down Expand Up @@ -537,6 +549,19 @@ function createBooleanAttributeArg(value: boolean) {
return arg;
}

function createNumberAttributeArg(value: number) {
const arg: AttributeArg = {
$type: 'AttributeArg',
} as any;
const expr: NumberLiteral = {
$type: 'NumberLiteral',
value: value.toString(),
$container: arg,
};
arg.value = expr;
return arg;
}

function createStringAttributeArg(value: string) {
const arg: AttributeArg = {
$type: 'AttributeArg',
Expand Down
33 changes: 33 additions & 0 deletions packages/auth-adapters/better-auth/test/auth-custom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { zenstackAdapter } from '../src/adapter';
import { betterAuth } from 'better-auth';

export const auth = betterAuth({
database: zenstackAdapter({} as any, {
provider: 'postgresql',
}),
user: {
additionalFields: {
role: {
type: ['user', 'admin'],
required: false,
defaultValue: 'user',
input: false, // don't allow user to set role
},
lang: {
type: 'string',
required: false,
defaultValue: 'en',
},
age: {
type: 'number',
required: true,
defaultValue: 18,
},
admin: {
type: 'boolean',
required: false,
defaultValue: false,
},
},
},
});
8 changes: 8 additions & 0 deletions packages/auth-adapters/better-auth/test/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { zenstackAdapter } from '../src/adapter';
import { betterAuth } from 'better-auth';

export const auth = betterAuth({
database: zenstackAdapter({} as any, {
provider: 'postgresql',
}),
});
65 changes: 65 additions & 0 deletions packages/auth-adapters/better-auth/test/cli-generate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { fileURLToPath } from 'node:url';
import tmp from 'tmp';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

/**
* Helper function to generate schema using better-auth CLI
*/
function generateSchema(configFile: string): string {
const { name: workDir } = tmp.dirSync({ unsafeCleanup: true });
const schemaPath = path.join(workDir, 'schema.zmodel');
const configPath = path.join(__dirname, configFile);

execSync(`pnpm better-auth generate --config ${configPath} --output ${schemaPath} --yes`, {
cwd: __dirname,
stdio: 'pipe',
});

return schemaPath;
}

/**
* Helper function to verify schema with zenstack check
*/
function verifySchema(schemaPath: string) {
const cliPath = path.join(__dirname, '../../../cli/dist/index.js');
const workDir = path.dirname(schemaPath);

expect(fs.existsSync(schemaPath)).toBe(true);

expect(() => {
execSync(`node ${cliPath} check --schema ${schemaPath}`, {
cwd: workDir,
stdio: 'pipe',
});
}).not.toThrow();
}

describe('Cli schema generation tests', () => {
it('works with simple config', async () => {
const schemaPath = generateSchema('auth.ts');
verifySchema(schemaPath);
});

it('works with custom config', async () => {
const schemaPath = generateSchema('auth-custom.ts');
verifySchema(schemaPath);

// Verify that the generated schema contains the expected default values
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
expect(schemaContent).toMatch(/role\s+String/);
expect(schemaContent).toContain("@default('user')");
expect(schemaContent).toMatch(/lang\s+String/);
expect(schemaContent).toContain("@default('en')");
expect(schemaContent).toMatch(/age\s+Int/);
expect(schemaContent).toContain('@default(18)');
expect(schemaContent).toMatch(/admin\s+Boolean/);
expect(schemaContent).toContain('@default(false)');
});
});
15 changes: 12 additions & 3 deletions 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.2.1",
"version": "3.3.0",
"type": "module",
"author": {
"name": "ZenStack Team"
Expand Down Expand Up @@ -38,30 +38,39 @@
"dependencies": {
"@zenstackhq/common-helpers": "workspace:*",
"@zenstackhq/language": "workspace:*",
"@zenstackhq/orm": "workspace:*",
"@zenstackhq/sdk": "workspace:*",
"@zenstackhq/server": "workspace:*",
"better-sqlite3": "catalog:",
"chokidar": "^5.0.0",
"colors": "1.4.0",
"commander": "^8.3.0",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"execa": "^9.6.0",
"express": "^5.0.0",
"jiti": "^2.6.1",
"langium": "catalog:",
"mixpanel": "^0.18.1",
"mysql2": "catalog:",
"ora": "^5.4.1",
"package-manager-detector": "^1.3.0",
"pg": "catalog:",
"prisma": "catalog:",
"semver": "^7.7.2",
"ts-pattern": "catalog:"
},
"devDependencies": {
"@types/better-sqlite3": "catalog:",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.0",
"@types/pg": "^8.16.0",
"@types/semver": "^7.7.0",
"@types/tmp": "catalog:",
"@zenstackhq/eslint-config": "workspace:*",
"@zenstackhq/orm": "workspace:*",
"@zenstackhq/testtools": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*",
"better-sqlite3": "catalog:",
"tmp": "catalog:"
},
"engines": {
Expand Down
12 changes: 12 additions & 0 deletions packages/cli/src/actions/action-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,15 @@ export async function requireDataSourceUrl(schemaFile: string) {
throw new CliError('The schema\'s "datasource" must have a "url" field to use this command.');
}
}

export function getOutputPath(options: { output?: string }, schemaFile: string) {
if (options.output) {
return options.output;
}
const pkgJsonConfig = getPkgJsonConfig(process.cwd());
if (pkgJsonConfig.output) {
return pkgJsonConfig.output;
} else {
return path.dirname(schemaFile);
}
}
Loading