diff --git a/code-repos/zenstackhq/v3-doc-orm b/code-repos/zenstackhq/v3-doc-orm index a3f7b09..d8777ab 160000 --- a/code-repos/zenstackhq/v3-doc-orm +++ b/code-repos/zenstackhq/v3-doc-orm @@ -1 +1 @@ -Subproject commit a3f7b09fa6b4c39d6582b3dae0504e95aab7cc21 +Subproject commit d8777abf227bffa300df12cac16889cbf8ad0aed diff --git a/code-repos/zenstackhq/v3-doc-orm-computed-fields b/code-repos/zenstackhq/v3-doc-orm-computed-fields index 05c609c..552d3ee 160000 --- a/code-repos/zenstackhq/v3-doc-orm-computed-fields +++ b/code-repos/zenstackhq/v3-doc-orm-computed-fields @@ -1 +1 @@ -Subproject commit 05c609c716ea30f7a30b094f828f51d0157ab2bd +Subproject commit 552d3eea41a4e32a9039bd19781df290f100474c diff --git a/code-repos/zenstackhq/v3-doc-orm-policy b/code-repos/zenstackhq/v3-doc-orm-policy index 33aec4f..ab1fedc 160000 --- a/code-repos/zenstackhq/v3-doc-orm-policy +++ b/code-repos/zenstackhq/v3-doc-orm-policy @@ -1 +1 @@ -Subproject commit 33aec4f07bccc094d202321c13393ddf356a10ac +Subproject commit ab1fedc5b57c1738dcf58a1b06163405565dc375 diff --git a/code-repos/zenstackhq/v3-doc-orm-polymorphism b/code-repos/zenstackhq/v3-doc-orm-polymorphism index a1b3e19..3ef64e7 160000 --- a/code-repos/zenstackhq/v3-doc-orm-polymorphism +++ b/code-repos/zenstackhq/v3-doc-orm-polymorphism @@ -1 +1 @@ -Subproject commit a1b3e19f5f911beee57181211c7bc4dc748ce8b6 +Subproject commit 3ef64e7d2a66d8bdfbf3c0ad379f4db9fea0aac1 diff --git a/code-repos/zenstackhq/v3-doc-orm-typed-json b/code-repos/zenstackhq/v3-doc-orm-typed-json index f04a076..7245f9a 160000 --- a/code-repos/zenstackhq/v3-doc-orm-typed-json +++ b/code-repos/zenstackhq/v3-doc-orm-typed-json @@ -1 +1 @@ -Subproject commit f04a076f104703a312510f943dedc831466b6139 +Subproject commit 7245f9acc2bb6e94acfb20d188e1a14b6fa288da diff --git a/code-repos/zenstackhq/v3-doc-orm-validation b/code-repos/zenstackhq/v3-doc-orm-validation index 42aa1d6..20014ef 160000 --- a/code-repos/zenstackhq/v3-doc-orm-validation +++ b/code-repos/zenstackhq/v3-doc-orm-validation @@ -1 +1 @@ -Subproject commit 42aa1d6e4ae1c1b6cffcca911a5c4fea365506c9 +Subproject commit 20014efeabbcb54685d017846bc17f9ab8427ea8 diff --git a/code-repos/zenstackhq/v3-doc-quick-start b/code-repos/zenstackhq/v3-doc-quick-start index f0d2f9f..64d688f 160000 --- a/code-repos/zenstackhq/v3-doc-quick-start +++ b/code-repos/zenstackhq/v3-doc-quick-start @@ -1 +1 @@ -Subproject commit f0d2f9f415d37463a6425077b78a7b81212215ee +Subproject commit 64d688f0b0908a5526e88eae5015a5615023fe3d diff --git a/code-repos/zenstackhq/v3-doc-server-adapter b/code-repos/zenstackhq/v3-doc-server-adapter index 229984e..4c71ba7 160000 --- a/code-repos/zenstackhq/v3-doc-server-adapter +++ b/code-repos/zenstackhq/v3-doc-server-adapter @@ -1 +1 @@ -Subproject commit 229984e1298c9fb94a55c94f2ccb242f4f67f8ae +Subproject commit 4c71ba7bb78791633305a8287d2ef2016b0636ef diff --git a/code-repos/zenstackhq/v3-doc-tanstack-query b/code-repos/zenstackhq/v3-doc-tanstack-query index b57abff..98a7a61 160000 --- a/code-repos/zenstackhq/v3-doc-tanstack-query +++ b/code-repos/zenstackhq/v3-doc-tanstack-query @@ -1 +1 @@ -Subproject commit b57abffec66d168bd3918b81776ddd1bb24fbeec +Subproject commit 98a7a610c0f63718386363f798ad2018197d56ed diff --git a/versioned_docs/version-3.x/migrate-prisma.md b/versioned_docs/version-3.x/migrate-prisma.md index d58681a..7ce0546 100644 --- a/versioned_docs/version-3.x/migrate-prisma.md +++ b/versioned_docs/version-3.x/migrate-prisma.md @@ -88,8 +88,8 @@ Replace `new PrismaClient()` with `new ZenStackClient(schema, ...)` where `schem ```ts title='db.ts' import { ZenStackClient } from '@zenstackhq/orm'; +import { PostgresDialect } from '@zenstackhq/orm/dialects/postgres'; import { schema } from './zenstack/schema'; -import { PostgresDialect } from 'kysely'; import { Pool } from 'pg'; export const db = new ZenStackClient(schema, { @@ -106,7 +106,7 @@ export const db = new ZenStackClient(schema, { ```ts title='db.ts' import { ZenStackClient } from '@zenstackhq/orm'; -import { SqliteDialect } from 'kysely'; +import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite'; import SQLite from 'better-sqlite3'; import { schema } from './zenstack/schema'; diff --git a/versioned_docs/version-3.x/orm/client.md b/versioned_docs/version-3.x/orm/client.md index 7ae246c..de786d6 100644 --- a/versioned_docs/version-3.x/orm/client.md +++ b/versioned_docs/version-3.x/orm/client.md @@ -26,7 +26,7 @@ The samples below only show creating a client using SQLite (via [better-sqlite3] ```ts title='db.ts' import { ZenStackClient } from '@zenstackhq/orm'; -import { SqliteDialect } from 'kysely'; +import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite'; import SQLite from 'better-sqlite3'; import { schema } from './zenstack/schema'; @@ -44,8 +44,8 @@ export const db = new ZenStackClient(schema, { ```ts title='db.ts' import { ZenStackClient } from '@zenstackhq/orm'; +import { PostgresDialect } from '@zenstackhq/orm/dialects/postgres'; import { schema } from './zenstack/schema'; -import { PostgresDialect } from 'kysely'; import { Pool } from 'pg'; export const db = new ZenStackClient(schema, { diff --git a/versioned_docs/version-3.x/recipe/databases/_category_.yml b/versioned_docs/version-3.x/recipe/databases/_category_.yml index 649e74c..bb2ee14 100644 --- a/versioned_docs/version-3.x/recipe/databases/_category_.yml +++ b/versioned_docs/version-3.x/recipe/databases/_category_.yml @@ -1,4 +1,4 @@ -position: 1 +position: 2 label: Databases collapsible: true collapsed: true diff --git a/versioned_docs/version-3.x/recipe/nestjs.md b/versioned_docs/version-3.x/recipe/nestjs.md new file mode 100644 index 0000000..982dde7 --- /dev/null +++ b/versioned_docs/version-3.x/recipe/nestjs.md @@ -0,0 +1,169 @@ +--- +sidebar_position: 4 +--- + +# Using ZenStack With NestJS + +This guide describes different ways to use ZenStack in a [NestJS](https://nestjs.com/) application. + +## As a Plain ORM + +ZenStack offers a standard ORM component that you can use in your NestJS application just like any other ORM. To get started, create a `DbService` by extending the `ZenStackClient` constructor: + +```ts title="db.service.ts" +import { ZenStackClient } from '@zenstackhq/orm'; +import { PostgresDialect } from '@zenstackhq/orm/dialects/postgres'; +import { schema, type SchemaType } from './zenstack/schema'; +import { Pool } from 'pg'; + +export class DbService extends ZenStackClient< + SchemaType, + ClientOptions +> { + constructor() { + super(schema, { + dialect: new PostgresDialect({ + pool: new Pool({ connectionString: process.env.DATABASE_URL }) + }), + }); + } +} +``` + +You can then register this service as a provider in a module: + +```ts title="app.module.ts" +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { DbService } from './db.service'; + +@Module({ + controllers: [AppController], + providers: [DbService], +}) +``` + +Finally, inject the `DbService` in your controllers or other services to access the database: + +```ts title="app.controller.ts" +import { Controller, Get } from '@nestjs/common'; +import { DbService } from './db.service'; + +@Controller('api') +export class AppController { + constructor(private readonly dbService: DbService) {} + + @Get('/posts') + getPosts() { + return this.dbService.post.findMany(); + } +} +``` + +## As an Access-Controlled ORM + +To leverage ZenStack's [built-in access control features](../orm/access-control/), you can inject a request-scoped `DbService` that's bound to the current session user (usually extracted from the request). + +In the sample below, we provide an access-controlled `DbService` under the name "AUTH_DB": + +```ts title="app.module.ts" +import { Module, Scope } from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; +import type { Request } from 'express'; +import { PolicyPlugin } from '@zenstackhq/plugin-policy'; +import { DbService } from './db.service'; +// your authentication helper +import { getRequestUser } from './auth.util'; + +@Module({ + controllers: [AppController], + providers: [ + // the standard (no-access-control) ORM + DbService, + + // the access-controlled ORM + { + provide: 'AUTH_DB', + + // make sure it's request-scoped to capture per-request user context + scope: Scope.REQUEST, + + useFactory: (req: Request, db: DbService) => { + // extract the current user from the request, implementation depends on + // your authentication solution + const user = getRequestUser(req); + + // install the PolicyPlugin and set the current user context + return db.$use(new PolicyPlugin()).$setAuth(user); + }, + + inject: [REQUEST, DbService], + }, + ], +}) +``` + +Now you can choose to use the access-controlled `DbService` in your controllers or services by injecting it with the "AUTH_DB" token: + +```ts title="app.controller.ts" +import { Controller, Get, Inject } from '@nestjs/common'; +import { DbService } from './db.service'; + +@Controller('api') +export class AppController { + // inject the access-controlled DbService + constructor(@Inject('AUTH_DB') private readonly dbService: DbService) {} + + @Get('/posts') + getPosts() { + // only posts accessible to the current user will be returned + return this.dbService.post.findMany(); + } +} +``` + +## As an Automatic CRUD Service + +ZenStack also provides [API handlers](../service/api-handler/) that process CRUD requests automatically for you. To use it, create a catch-all route in your controller and forward the request to the API handler: + +```ts title="app.controller.ts" +import { Controller, All, Inject, Param, Query, Req, Res } from '@nestjs/common'; +import { RestApiHandler } from '@zenstackhq/server/api'; +import type { Request, Response } from 'express'; +import { DbService } from './db.service'; + +@Controller('api') +export class AppController { + // RESTful API Handler is used here for demonstration. You can also use the + // RPC-style API handler if preferred. See the API handlers docs for more + // details. + private readonly apiHandler = new RestApiHandler({ + schema, + endpoint: 'http://localhost:3000/api', + }); + + constructor(@Inject('AUTH_DB') private readonly dbService: DbService) {} + + @All('/*path') + async handleAll( + @Req() req: Request, + @Res() res: Response, + @Param('path') path: string[], + @Query() query: Record, + ) { + // forward the request to the API handler + const result = await this.apiHandler.handleRequest({ + method: req.method, + path: path.join('/'), + query, + requestBody: req.body, + client: this.dbService, + }); + + res.status(result.status).json(result.body); + } +} +``` + +With this setup, all requests to `/api/*` will be handled by the ZenStack API handler, which performs the necessary CRUD operations with access control enforced. Refer to the [API handlers documentation](../service/api-handler/) for request and response formats and other details. + diff --git a/versioned_docs/version-3.x/recipe/postgres-multi-schema.md b/versioned_docs/version-3.x/recipe/postgres-multi-schema.md index fd886ec..3669876 100644 --- a/versioned_docs/version-3.x/recipe/postgres-multi-schema.md +++ b/versioned_docs/version-3.x/recipe/postgres-multi-schema.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- # Working With PostgreSQL Schemas