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
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Even without using advanced features, ZenStack offers the following benefits as

# Get started

> You can also check the [blog sample](./samples/blog) for a complete example.

## Installation

### 1. Creating a new project
Expand Down Expand Up @@ -136,7 +138,9 @@ Now you can use the compiled TypeScript schema to instantiate a database client:
import { ZenStackClient } from '@zenstackhq/runtime';
import { schema } from './zenstack/schema';

const client = new ZenStackClient(schema);
const client = new ZenStackClient(schema, {
dialectConfig: { ... }
});
```

## Using `ZenStackClient`
Expand Down Expand Up @@ -207,14 +211,15 @@ ZenStack v3 allows you to define database-evaluated computed fields with the fol
model User {
...
/// number of posts owned by the user
postCount Int @computed
postCount Int @computed
}
```

2. Provide its implementation using query builder when constructing `ZenStackClient`

```ts
const client = new ZenStackClient(schema, {
...
computedFields: {
User: {
postCount: (eb) =>
Expand Down Expand Up @@ -367,7 +372,7 @@ See [Prisma Migrate](https://www.prisma.io/docs/orm/prisma-migrate) documentatio
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. Replace `new PrismaClient()` with `new ZenStackClient(schema)`
1. Replace `new PrismaClient()` with `new ZenStackClient(schema, { ... })`

# Limitations

Expand Down
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
- [x] Custom table name
- [x] Custom field name
- [ ] Strict undefined check
- [ ] Implement changesets
- [ ] Polymorphism
- [ ] Validation
- [ ] Access Policy
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/actions/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export async function run(options: Options) {
import { ZenStackClient } from '@zenstackhq/runtime';
import { schema } from '${outputPath}/schema';

const client = new ZenStackClient(schema);
const client = new ZenStackClient(schema, {
dialectConfig: { ... }
});
\`\`\`
`);
}
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/actions/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ model Post {

export const STARTER_MAIN_TS = `import { ZenStackClient } from '@zenstackhq/runtime';
import { schema } from './zenstack/schema';
import SQLite from 'better-sqlite3';

async function main() {
const client = new ZenStackClient(schema);
const client = new ZenStackClient(schema, {
dialectConfig: {
database: new SQLite('./zenstack/dev.db'),
},
});
const user = await client.user.create({
data: {
email: '[email protected]',
Expand Down
1 change: 0 additions & 1 deletion packages/cli/test/ts-schema-gen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ model Post {

expect(schema.provider).toMatchObject({
type: 'sqlite',
dialectConfigProvider: expect.any(Function),
});

expect(schema.models).toMatchObject({
Expand Down
48 changes: 42 additions & 6 deletions packages/create-zenstack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,18 @@ import { STARTER_MAIN_TS, STARTER_ZMODEL } from './templates';
const npmAgent = process.env['npm_config_user_agent'];
let agent = 'npm';
let agentExec = 'npx';
let initCommand = 'npm init -y';
let saveDev = '--save-dev';

if (npmAgent?.includes('pnpm')) {
agent = 'pnpm';
initCommand = 'pnpm init';
agentExec = 'pnpm';
} else if (npmAgent?.includes('yarn')) {
agent = 'yarn';
initCommand = 'yarn init';
agentExec = 'npx';
saveDev = '--dev';
} else if (npmAgent?.includes('bun')) {
agent = 'bun';
agentExec = 'bun';
initCommand = 'bun init -y -m';
}

const program = new Command('create-zenstack');
Expand All @@ -46,20 +42,60 @@ function initProject(name: string) {

console.log(colors.gray(`Using package manager: ${agent}`));

// initialize project
execSync(initCommand);
// create package.json
fs.writeFileSync(
'package.json',
JSON.stringify(
{
name: 'zenstack-app',
version: '1.0.0',
description: 'Scaffolded with create-zenstack',
type: 'module',
scripts: {
dev: 'tsx main.ts',
},
license: 'ISC',
},
null,
2
)
);

// install packages
const packages = [
{ name: '@zenstackhq/cli@next', dev: true },
{ name: '@zenstackhq/runtime@next', dev: false },
{ name: 'better-sqlite3', dev: false },
{ name: '@types/better-sqlite3', dev: true },
{ name: 'typescript', dev: true },
{ name: 'tsx', dev: true },
{ name: '@types/node', dev: true },
];
for (const pkg of packages) {
installPackage(pkg);
}

// create tsconfig.json
fs.writeFileSync(
'tsconfig.json',
JSON.stringify(
{
compilerOptions: {
module: 'esnext',
target: 'esnext',
moduleResolution: 'bundler',
sourceMap: true,
outDir: 'dist',
strict: true,
skipLibCheck: true,
esModuleInterop: true,
},
},
null,
2
)
);

// create schema.zmodel
fs.mkdirSync('zenstack');
fs.writeFileSync('zenstack/schema.zmodel', STARTER_ZMODEL);
Expand Down
21 changes: 0 additions & 21 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,6 @@
"default": "./dist/plugins/policy.cjs"
}
},
"./utils/pg-utils": {
"import": {
"types": "./dist/utils/pg-utils.d.ts",
"default": "./dist/utils/pg-utils.js"
},
"require": {
"types": "./dist/utils/pg-utils.d.cts",
"default": "./dist/utils/pg-utils.cjs"
}
},
"./utils/sqlite-utils": {
"import": {
"types": "./dist/utils/sqlite-utils.d.ts",
"default": "./dist/utils/sqlite-utils.js"
},
"require": {
"types": "./dist/utils/sqlite-utils.d.cts",
"default": "./dist/utils/sqlite-utils.cjs"
}
},
"./package.json": {
"import": "./package.json",
"require": "./package.json"
Expand All @@ -91,7 +71,6 @@
"json-stable-stringify": "^1.3.0",
"kysely": "^0.27.5",
"nanoid": "^5.0.9",
"pg-connection-string": "^2.9.0",
"tiny-invariant": "^1.3.3",
"ts-pattern": "^5.6.0",
"ulid": "^3.0.0",
Expand Down
24 changes: 9 additions & 15 deletions packages/runtime/src/client/client-impl.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SqliteDialectConfig } from 'kysely';
import {
DefaultConnectionProvider,
DefaultQueryExecutor,
Expand All @@ -7,7 +8,6 @@ import {
SqliteDialect,
type KyselyProps,
type PostgresDialectConfig,
type SqliteDialectConfig,
} from 'kysely';
import { match } from 'ts-pattern';
import type { GetModels, ProcedureDef, SchemaDef } from '../schema';
Expand Down Expand Up @@ -41,7 +41,7 @@ import { ResultProcessor } from './result-processor';
export const ZenStackClient = function <Schema extends SchemaDef>(
this: any,
schema: any,
options?: ClientOptions<Schema>
options: ClientOptions<Schema>
) {
return new ClientImpl<Schema>(schema, options);
} as unknown as ClientConstructor;
Expand All @@ -56,7 +56,7 @@ export class ClientImpl<Schema extends SchemaDef> {

constructor(
private readonly schema: Schema,
private options?: ClientOptions<Schema>,
private options: ClientOptions<Schema>,
baseClient?: ClientImpl<Schema>
) {
this.$schema = schema;
Expand Down Expand Up @@ -140,21 +140,15 @@ export class ClientImpl<Schema extends SchemaDef> {
}

private makePostgresKyselyDialect(): PostgresDialect {
const { dialectConfigProvider } = this.schema.provider;
const mergedConfig = {
...dialectConfigProvider(),
...this.options?.dialectConfig,
} as PostgresDialectConfig;
return new PostgresDialect(mergedConfig);
return new PostgresDialect(
this.options.dialectConfig as PostgresDialectConfig
);
}

private makeSqliteKyselyDialect(): SqliteDialect {
const { dialectConfigProvider } = this.schema.provider;
const mergedConfig = {
...dialectConfigProvider(),
...this.options?.dialectConfig,
} as SqliteDialectConfig;
return new SqliteDialect(mergedConfig);
return new SqliteDialect(
this.options.dialectConfig as SqliteDialectConfig
);
}

async $transaction<T>(
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/client/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export type ClientOptions<Schema extends SchemaDef> = {
/**
* Database dialect configuration.
*/
dialectConfig?: DialectConfig<Schema['provider']>;
dialectConfig: DialectConfig<Schema['provider']>;

/**
* Custom function definitions.
Expand Down
12 changes: 0 additions & 12 deletions packages/runtime/src/utils/pg-utils.ts

This file was deleted.

21 changes: 0 additions & 21 deletions packages/runtime/src/utils/sqlite-utils.ts

This file was deleted.

8 changes: 3 additions & 5 deletions packages/runtime/test/client-api/default-values.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import { ExpressionUtils, type SchemaDef } from '../../src/schema';
const schema = {
provider: {
type: 'sqlite',
dialectConfigProvider: () =>
({
database: new SQLite(':memory:'),
} as any),
},
models: {
Model: {
Expand Down Expand Up @@ -68,7 +64,9 @@ const schema = {

describe('default values tests', () => {
it('supports generators', async () => {
const client = new ZenStackClient(schema);
const client = new ZenStackClient(schema, {
dialectConfig: { database: new SQLite(':memory:') },
});
await client.$pushSchema();

const entity = await client.model.create({ data: {} });
Expand Down
11 changes: 6 additions & 5 deletions packages/runtime/test/client-api/name-mapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ describe('Name mapping tests', () => {
const schema = {
provider: {
type: 'sqlite',
dialectConfigProvider: () => ({
database: new SQLite(':memory:'),
}),
},
models: {
Foo: {
Expand Down Expand Up @@ -58,7 +55,9 @@ describe('Name mapping tests', () => {
} as const satisfies SchemaDef;

it('works with model and implicit field mapping', async () => {
const client = new ZenStackClient(schema);
const client = new ZenStackClient(schema, {
dialectConfig: { database: new SQLite(':memory:') },
});
await client.$pushSchema();
const r1 = await client.foo.create({
data: { id: '1', x: 1 },
Expand Down Expand Up @@ -88,7 +87,9 @@ describe('Name mapping tests', () => {
});

it('works with explicit field mapping', async () => {
const client = new ZenStackClient(schema);
const client = new ZenStackClient(schema, {
dialectConfig: { database: new SQLite(':memory:') },
});
await client.$pushSchema();
const r1 = await client.foo.create({
data: { id: '1', x: 1 },
Expand Down
11 changes: 7 additions & 4 deletions packages/runtime/test/plugin/kysely-on-query.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import SQLite from 'better-sqlite3';
import {
InsertQueryNode,
Kysely,
Expand All @@ -13,19 +14,21 @@ describe('Kysely onQuery tests', () => {
let _client: ClientContract<typeof schema>;

beforeEach(async () => {
_client = new ZenStackClient(schema);
_client = new ZenStackClient(schema, {
dialectConfig: { database: new SQLite(':memory:') },
});
await _client.$pushSchema();
});

it('intercepts queries', async () => {
let called = false;
const client = _client.$use({
id: 'test-plugin',
onKyselyQuery(args) {
if (args.query.kind === 'InsertQueryNode') {
onKyselyQuery({ query, proceed }) {
if (query.kind === 'InsertQueryNode') {
called = true;
}
return args.proceed(args.query);
return proceed(query);
},
});
await expect(
Expand Down
Loading