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
45 changes: 22 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ npm install -D @types/pg
Run the following command to sync schema to the database for local development:

```bash
npx zenstack db push
npx zen db push
```

> Under the hood, the command uses `prisma db push` to do the job.
Expand All @@ -127,17 +127,17 @@ See [database migration](#database-migration) for how to use migration to manage

## Compiling ZModel schema

ZModel needs to be compiled to TypeScript before being used to create a database client. Simply run the following command:
ZModel needs to be compiled to TypeScript before being used to create a database db. Simply run the following command:

```bash
npx zenstack generate
npx zen generate
```

A `schema.ts` file will be created inside the `zenstack` folder. The file should be included as part of your source tree for compilation/bundling. You may choose to include or ignore it in source control (and generate on the fly during build). Just remember to rerun the "generate" command whenever you make changes to the ZModel schema.

## Creating ZenStack client

Now you can use the compiled TypeScript schema to instantiate a database client.
Now you can use the compiled TypeScript schema to instantiate a database db.

### SQLite

Expand All @@ -147,7 +147,7 @@ import { schema } from './zenstack/schema';
import SQLite from 'better-sqlite3';
import { SqliteDialect } from 'kysely';

const client = new ZenStackClient(schema, {
const db = new ZenStackClient(schema, {
dialect: new SqliteDialect({ database: new SQLite('./dev.db') }),
});
```
Expand All @@ -159,11 +159,10 @@ import { ZenStackClient } from '@zenstackhq/runtime';
import { schema } from './zenstack/schema';
import { PostgresDialect } from 'kysely';
import { Pool } from 'pg';
import { parseIntoClientConfig } from 'pg-connection-string';

const client = new ZenStackClient(schema, {
const db = new ZenStackClient(schema, {
dialect: new PostgresDialect({
pool: new Pool(parseIntoClientConfig(process.env.DATABASE_URL)),
pool: new Pool({ connectionString: process.env.DATABASE_URL }),
}),
});
```
Expand All @@ -177,20 +176,20 @@ const client = new ZenStackClient(schema, {
A few quick examples:

```ts
const user = await client.user.create({
const user = await db.user.create({
data: {
name: 'Alex',
email: '[email protected]',
posts: { create: { title: 'Hello world' } },
},
});

const userWithPosts = await client.user.findUnique({
const userWithPosts = await db.user.findUnique({
where: { id: user.id },
include: { posts: true },
});

const groupedPosts = await client.post.groupBy({
const groupedPosts = await db.post.groupBy({
by: 'published',
_count: true,
});
Expand All @@ -205,7 +204,7 @@ ZenStack uses Kysely to handle database operations, and it also directly exposes
Please check [Kysely documentation](https://kysely.dev/docs/intro) for more details. Here're a few quick examples:

```ts
await client.$qb
await db.$qb
.selectFrom('User')
.leftJoin('Post', 'Post.authorId', 'User.id')
.select(['User.id', 'User.email', 'Post.title'])
Expand All @@ -215,7 +214,7 @@ await client.$qb
Query builder can also be "blended" into ORM API calls as a local escape hatch for building complex filter conditions. It allows for greater flexibility without forcing you to entirely resort to the query builder API.

```ts
await client.user.findMany({
await db.user.findMany({
where: {
age: { gt: 18 },
// "eb" is a Kysely expression builder
Expand Down Expand Up @@ -243,7 +242,7 @@ ZenStack v3 allows you to define database-evaluated computed fields with the fol
2. Provide its implementation using query builder when constructing `ZenStackClient`

```ts
const client = new ZenStackClient(schema, {
const db = new ZenStackClient(schema, {
...
computedFields: {
User: {
Expand Down Expand Up @@ -279,7 +278,7 @@ _Coming soon..._

### Runtime plugins

V3 introduces a new runtime plugin mechanism that allows you to tap into the ORM's query execution in various ways. A plugin implements the [RuntimePlugin](./packages/runtime/src/client/plugin.ts#L121) interface, and can be installed with the `ZenStackClient.$use` API.
V3 introduces a new runtime plugin mechanism that allows you to tap into the ORM's query execution in various ways. A plugin implements the [RuntimePlugin](./packages/runtime/src/client/plugin.ts#L121) interface, and can be installed with the `ZenStackdb.$use` API.

You can use a plugin to achieve the following goals:

Expand All @@ -288,7 +287,7 @@ You can use a plugin to achieve the following goals:
ORM query interception allows you to intercept the high-level ORM API calls. The interceptor's configuration is compatible with Prisma's [query client extension](https://www.prisma.io/docs/orm/prisma-client/client-extensions/query).

```ts
client.$use({
db.$use({
id: 'cost-logger',
onQuery: {
$allModels: {
Expand All @@ -312,7 +311,7 @@ Kysely query interception allows you to intercept the low-level query builder AP
Kysely query interception works against the low-level Kysely `OperationNode` structures. It's harder to implement but can guarantee intercepting all CRUD operations.

```ts
client.$use({
db.$use({
id: 'insert-interception-plugin',
onKyselyQuery({query, proceed}) {
if (query.kind === 'InsertQueryNode') {
Expand All @@ -332,7 +331,7 @@ function sanitizeInsertData(query: InsertQueryNode) {
Another popular interception use case is, instead of intercepting calls, "listening on" entity changes.

```ts
client.$use({
db.$use({
id: 'mutation-hook-plugin',
beforeEntityMutation({ model, action }) {
console.log(`Before ${model} ${action}`);
Expand All @@ -346,7 +345,7 @@ client.$use({
You can provide an extra `mutationInterceptionFilter` to control what to intercept, and opt in for loading the affected entities before and/or after the mutation.

```ts
client.$use({
db.$use({
id: 'mutation-hook-plugin',
mutationInterceptionFilter: ({ model }) => {
return {
Expand Down Expand Up @@ -375,19 +374,19 @@ ZenStack v3 delegates database schema migration to Prisma. The CLI provides Pris
- Sync schema to dev database and create a migration record:

```bash
npx zenstack migrate dev
npx zen migrate dev
```

- Deploy new migrations:

```bash
npx zenstack migrate deploy
npx zen migrate deploy
```

- Reset dev database

```bash
npx zenstack migrate reset
npx zen migrate reset
```

See [Prisma Migrate](https://www.prisma.io/docs/orm/prisma-migrate) documentation for more details.
Expand All @@ -398,7 +397,7 @@ See [Prisma Migrate](https://www.prisma.io/docs/orm/prisma-migrate) documentatio
1. Remove "@prisma/client" dependency
1. Install "better-sqlite3" or "pg" based on database type
1. Move "schema.prisma" to "zenstack" folder and rename it to "schema.zmodel"
1. Run `npx zenstack generate`
1. Run `npx zen generate`
1. Replace `new PrismaClient()` with `new ZenStackClient(schema, { ... })`

# Limitations
Expand Down
5 changes: 5 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
- [x] validate
- [ ] format
- [ ] db seed
- [ ] plugin mechanism
- [ ] built-in plugins
- [ ] ts
- [ ] prisma
- [ ] ZModel
- [ ] Import
- [ ] View support
Expand Down Expand Up @@ -73,6 +77,7 @@
- [x] Compound ID
- [ ] Cross field comparison
- [x] Many-to-many relation
- [ ] Self relation
- [ ] Empty AND/OR/NOT behavior
- [x] Logging
- [x] Error system
Expand Down
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-alpha.16",
"version": "3.0.0-alpha.17",
"description": "ZenStack",
"packageManager": "[email protected]",
"scripts": {
Expand Down
3 changes: 2 additions & 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-alpha.16",
"version": "3.0.0-alpha.17",
"type": "module",
"author": {
"name": "ZenStack Team"
Expand Down Expand Up @@ -49,6 +49,7 @@
"@zenstackhq/runtime": "workspace:*",
"@zenstackhq/testtools": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*",
"better-sqlite3": "^11.8.1",
"tmp": "catalog:"
}
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/actions/action-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ export function getSchemaFile(file?: string) {
export async function loadSchemaDocument(schemaFile: string) {
const loadResult = await loadDocument(schemaFile);
if (!loadResult.success) {
console.error(colors.red('Error loading schema:'));
loadResult.errors.forEach((err) => {
console.error(colors.red(err));
});
throw new CliError('Failed to load schema');
throw new CliError('Schema contains errors. See above for details.');
}
loadResult.warnings.forEach((warn) => {
console.warn(colors.yellow(warn));
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/actions/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ import { schema } from '${outputPath}/schema';
const client = new ZenStackClient(schema, {
dialect: { ... }
});
\`\`\`
`);
\`\`\``);
}
}

Expand Down
18 changes: 16 additions & 2 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ZModelLanguageMetaData } from '@zenstackhq/language';
import colors from 'colors';
import { Command, Option } from 'commander';
import { Command, CommanderError, Option } from 'commander';
import * as actions from './actions';
import { CliError } from './cli-error';
import { getVersion } from './utils/version-utils';

const generateAction = async (options: Parameters<typeof actions.generate>[0]): Promise<void> => {
Expand Down Expand Up @@ -125,4 +126,17 @@ export function createProgram() {
}

const program = createProgram();
program.parse(process.argv);

program.parseAsync().catch((err) => {
if (err instanceof CliError) {
console.error(colors.red(err.message));
process.exit(1);
} else if (err instanceof CommanderError) {
// errors are already reported, just exit
process.exit(err.exitCode);
} else {
console.error(colors.red('An unexpected error occurred:'));
console.error(err);
process.exit(1);
}
});
2 changes: 1 addition & 1 deletion packages/cli/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import base from '@zenstackhq/vitest-config/base';
import { defineConfig, mergeConfig } from 'vitest/config';
import base from '../../vitest.base.config';

export default mergeConfig(base, defineConfig({}));
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-alpha.16",
"version": "3.0.0-alpha.17",
"description": "ZenStack Common Helpers",
"type": "module",
"scripts": {
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-alpha.16",
"version": "3.0.0-alpha.17",
"description": "Create a new ZenStack project",
"type": "module",
"scripts": {
Expand Down
25 changes: 25 additions & 0 deletions packages/dialects/sql.js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Forked from https://github.com/betarixm/kysely-sql-js

## Usage

```ts
import { type GeneratedAlways, Kysely } from 'kysely';
import initSqlJs from 'sql.js';

import { SqlJsDialect } from '@zenstackhq/kysely-sql-js';

interface Database {
person: {
id: GeneratedAlways<number>;
first_name: string | null;
last_name: string | null;
age: number;
};
}

const SqlJsStatic = await initSqlJs();

export const db = new Kysely<Database>({
dialect: new SqlJsDialect({ sqlJs: new SqlJsStatic.Database() }),
});
```
4 changes: 4 additions & 0 deletions packages/dialects/sql.js/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import config from '@zenstackhq/eslint-config/base.js';

/** @type {import("eslint").Linter.Config} */
export default config;
42 changes: 42 additions & 0 deletions packages/dialects/sql.js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@zenstackhq/kysely-sql-js",
"version": "3.0.0-alpha.17",
"description": "Kysely dialect for sql.js",
"type": "module",
"scripts": {
"build": "tsup-node",
"watch": "tsup-node --watch",
"lint": "eslint src --ext ts",
"pack": "pnpm pack"
},
"keywords": [],
"author": "ZenStack Team",
"license": "MIT",
"files": [
"dist"
],
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"devDependencies": {
"@types/sql.js": "^1.4.9",
"@zenstackhq/eslint-config": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*",
"sql.js": "^1.13.0",
"kysely": "catalog:"
},
"peerDependencies": {
"sql.js": "^1.13.0",
"kysely": "catalog:"
}
}
Loading
Loading