diff --git a/docusaurus.config.js b/docusaurus.config.js index 0dd2e501..5eef4d59 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -44,6 +44,10 @@ const config = { label: '1.x', banner: 'none', }, + '3.x': { + label: '3.0 Alpha', + banner: 'none', + }, }, }, blog: false, diff --git a/package.json b/package.json index fa70489e..d5b8de74 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,10 @@ "@docusaurus/theme-mermaid": "3.4.0", "@giscus/react": "^2.4.0", "@mdx-js/react": "^3.0.1", + "@stackblitz/sdk": "^1.11.0", "autoprefixer": "^10.4.13", "clsx": "^1.2.1", + "is-mobile": "^5.0.0", "postcss": "^8.4.21", "prism-react-renderer": "^2.3.1", "prism-svelte": "^0.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78043273..ec59cc8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,12 +29,18 @@ importers: '@mdx-js/react': specifier: ^3.0.1 version: 3.0.1(@types/react@18.0.26)(react@18.2.0) + '@stackblitz/sdk': + specifier: ^1.11.0 + version: 1.11.0 autoprefixer: specifier: ^10.4.13 version: 10.4.13(postcss@8.4.21) clsx: specifier: ^1.2.1 version: 1.2.1 + is-mobile: + specifier: ^5.0.0 + version: 5.0.0 postcss: specifier: ^8.4.21 version: 8.4.21 @@ -1113,6 +1119,9 @@ packages: '@slorber/remark-comment@1.0.0': resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==} + '@stackblitz/sdk@1.11.0': + resolution: {integrity: sha512-DFQGANNkEZRzFk1/rDP6TcFdM82ycHE+zfl9C/M/jXlH68jiqHWHFMQURLELoD8koxvu/eW5uhg94NSAZlYrUQ==} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} @@ -3010,6 +3019,9 @@ packages: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} + is-mobile@5.0.0: + resolution: {integrity: sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==} + is-npm@6.0.0: resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6914,6 +6926,8 @@ snapshots: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 + '@stackblitz/sdk@1.11.0': {} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 @@ -9091,6 +9105,8 @@ snapshots: global-dirs: 3.0.1 is-path-inside: 3.0.3 + is-mobile@5.0.0: {} + is-npm@6.0.0: {} is-number@7.0.0: {} diff --git a/src/components/GithubCodeBlock.tsx b/src/components/GithubCodeBlock.tsx new file mode 100644 index 00000000..5ab8770a --- /dev/null +++ b/src/components/GithubCodeBlock.tsx @@ -0,0 +1,40 @@ +import CodeBlock from '@theme/CodeBlock'; +import { useEffect, useState } from 'react'; + +interface GithubCodeBlockProps { + repoPath: string; + file: string; +} + +const GithubCodeBlock: React.FC = ({ repoPath, file }) => { + const [code, setCode] = useState('Loading...'); + + useEffect(() => { + (async function () { + const response = await fetch(`https://cdn.jsdelivr.net/gh/${repoPath}/${file}`); + if (!response.ok) { + setCode(`Unable to load "${repoPath}/${file}"`); + return; + } + const text = await response.text(); + setCode(text); + })(); + }, [repoPath, file]); + + const getLanguage = (file: string): string => { + if (file.endsWith('.ts')) { + return 'typescript'; + } else if (file.endsWith('.zmodel')) { + return 'zmodel'; + } else { + return 'plaintext'; + } + }; + return ( + + {code} + + ); +}; + +export default GithubCodeBlock; diff --git a/src/components/StackBlitzEmbed.tsx b/src/components/StackBlitzEmbed.tsx new file mode 100644 index 00000000..db7b6418 --- /dev/null +++ b/src/components/StackBlitzEmbed.tsx @@ -0,0 +1,26 @@ +import React, { useEffect, useRef } from 'react'; +import sdk from '@stackblitz/sdk'; + +interface StackBlitzEmbedProps { + projectId: string; + height?: string; +} + +const StackBlitzEmbed: React.FC = ({ projectId, height = '600px' }) => { + const containerRef = useRef(null); + + useEffect(() => { + if (containerRef.current) { + sdk.embedProjectId(containerRef.current, projectId, { + openFile: 'main.ts', + height, + view: 'editor', + forceEmbedLayout: true, + }); + } + }, [projectId, height]); + + return
; +}; + +export default StackBlitzEmbed; diff --git a/src/components/StackBlitzGithubEmbed.tsx b/src/components/StackBlitzGithubEmbed.tsx new file mode 100644 index 00000000..de68def6 --- /dev/null +++ b/src/components/StackBlitzGithubEmbed.tsx @@ -0,0 +1,84 @@ +import sdk from '@stackblitz/sdk'; +import TabItem from '@theme/TabItem'; +import Tabs from '@theme/Tabs'; +import mobile from 'is-mobile'; +import React, { useEffect, useRef } from 'react'; +import GithubCodeBlock from './GithubCodeBlock'; + +interface StackBlitzGithubEmbedProps { + repoPath: string; + height?: string; + openFile?: string; + plainCodeFiles?: string[]; + startScript?: string; + clickToLoad?: boolean; +} + +const StackBlitzGithubEmbed: React.FC = ({ + repoPath, + height = '600px', + openFile = 'main.ts', + plainCodeFiles = undefined, + clickToLoad = false, + startScript, +}) => { + const containerRef = useRef(null); + + const options = { + openFile, + height, + view: 'editor', + startScript, + clickToLoad, + } as const; + + if (!plainCodeFiles) { + plainCodeFiles = [openFile]; + } + + useEffect(() => { + if (containerRef.current) { + setTimeout(() => { + // docusaurus seems to recreate the tab after mounting, give it a + // chance to complete + sdk.embedGithubProject(containerRef.current, repoPath, options); + }, 0); + } + }, [repoPath, height, containerRef]); + + const PlainCode = () => ( + <> + {plainCodeFiles.map((file) => ( + + ))} + + ); + + if (mobile()) { + return ; + } else { + return ( + <> + + +
+ Click{' '} + sdk.openGithubProject(repoPath, options)}> + here + {' '} + to pop out if the embed doesn't load an interactive terminal. +
+
+ + + + + + +
+ + ); + } +}; + +export default StackBlitzGithubEmbed; diff --git a/versioned_docs/version-3.x/_components/PackageExec.tsx b/versioned_docs/version-3.x/_components/PackageExec.tsx new file mode 100644 index 00000000..e3df2110 --- /dev/null +++ b/versioned_docs/version-3.x/_components/PackageExec.tsx @@ -0,0 +1,28 @@ +import CodeBlock from '@theme/CodeBlock'; +import TabItem from '@theme/TabItem'; +import Tabs from '@theme/Tabs'; + +interface Props { + command: string; +} + +const pkgManagers = [ + { name: 'npm', command: 'npx' }, + { name: 'pnpm', command: 'pnpm' }, + { name: 'bun', command: 'bunx' }, + { name: 'yarn', command: 'npx' }, +]; + +const PackageInstall = ({ command }: Props) => { + return ( + + {pkgManagers.map((pkg) => ( + + {`${pkg.command} ${command}`} + + ))} + + ); +}; + +export default PackageInstall; diff --git a/versioned_docs/version-3.x/_components/PackageInstall.tsx b/versioned_docs/version-3.x/_components/PackageInstall.tsx new file mode 100644 index 00000000..53bf8805 --- /dev/null +++ b/versioned_docs/version-3.x/_components/PackageInstall.tsx @@ -0,0 +1,33 @@ +import CodeBlock from '@theme/CodeBlock'; +import TabItem from '@theme/TabItem'; +import Tabs from '@theme/Tabs'; + +interface Props { + devDependencies: string[]; + dependencies: string[]; +} + +const pkgManagers = [ + { name: 'npm', command: 'npm install', dev: '--save-dev' }, + { name: 'pnpm', command: 'pnpm add', dev: '--save-dev' }, + { name: 'bun', command: 'bun add', dev: '--dev' }, + { name: 'yarn', command: 'yarn add', dev: '--dev' }, +]; + +const PackageInstall = ({ devDependencies, dependencies }: Props) => { + return ( + + {pkgManagers.map((pkg) => ( + + + {`${devDependencies?.length ? `${pkg.command} ${pkg.dev} ${devDependencies.join(' ')}\n` : ''}${ + dependencies?.length ? `${pkg.command} ${dependencies.join(' ')}` : '' + }`} + + + ))} + + ); +}; + +export default PackageInstall; diff --git a/versioned_docs/version-3.x/_components/ZModelVsPSL.tsx b/versioned_docs/version-3.x/_components/ZModelVsPSL.tsx new file mode 100644 index 00000000..46b7a50d --- /dev/null +++ b/versioned_docs/version-3.x/_components/ZModelVsPSL.tsx @@ -0,0 +1,16 @@ +import React, { FC } from 'react'; +import Admonition from '@theme/Admonition'; + +interface ZModelVsPSLProps { + children: React.ReactNode; +} + +const ZModelVsPSL: FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export default ZModelVsPSL; diff --git a/versioned_docs/version-3.x/_components/ZenStackVsPrisma.tsx b/versioned_docs/version-3.x/_components/ZenStackVsPrisma.tsx new file mode 100644 index 00000000..323adb57 --- /dev/null +++ b/versioned_docs/version-3.x/_components/ZenStackVsPrisma.tsx @@ -0,0 +1,16 @@ +import React, { FC } from 'react'; +import Admonition from '@theme/Admonition'; + +interface ZenStackVsPrismaProps { + children: React.ReactNode; +} + +const ZenStackVsPrisma: FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export default ZenStackVsPrisma; diff --git a/versioned_docs/version-3.x/_components/_zmodel-starter.md b/versioned_docs/version-3.x/_components/_zmodel-starter.md new file mode 100644 index 00000000..fbf3eaea --- /dev/null +++ b/versioned_docs/version-3.x/_components/_zmodel-starter.md @@ -0,0 +1,23 @@ + ```zmodel + datasource db { + provider = 'sqlite' + url = "file:./dev.db" + } + + model User { + id String @id @default(cuid()) + email String @unique + posts Post[] + } + + model Post { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + title String + content String + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId String + } + ``` \ No newline at end of file diff --git a/versioned_docs/version-3.x/faq.md b/versioned_docs/version-3.x/faq.md new file mode 100644 index 00000000..410fe76c --- /dev/null +++ b/versioned_docs/version-3.x/faq.md @@ -0,0 +1,14 @@ +--- +description: ZenStack FAQ. + +slug: /faq +sidebar_label: FAQ +sidebar_position: 100 +--- + +# πŸ™‹πŸ» FAQ + +## What databases are supported? + +Currently only SQLite and PostgreSQL are supported. MySQL will be added in the future. There's no plan to support other relational databases or NoSQL databases. + diff --git a/versioned_docs/version-3.x/migration/_category_.yml b/versioned_docs/version-3.x/migration/_category_.yml new file mode 100644 index 00000000..6cb70365 --- /dev/null +++ b/versioned_docs/version-3.x/migration/_category_.yml @@ -0,0 +1,4 @@ +position: 4 +label: Migration +collapsible: true +collapsed: true diff --git a/versioned_docs/version-3.x/migration/index.md b/versioned_docs/version-3.x/migration/index.md new file mode 100644 index 00000000..9fe870b2 --- /dev/null +++ b/versioned_docs/version-3.x/migration/index.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 1 +description: ZenStack migration overview +--- + +# Overview diff --git a/versioned_docs/version-3.x/modeling/_category_.yml b/versioned_docs/version-3.x/modeling/_category_.yml new file mode 100644 index 00000000..2df4bdd6 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/_category_.yml @@ -0,0 +1,4 @@ +position: 2 +label: Data Modeling +collapsible: true +collapsed: true diff --git a/versioned_docs/version-3.x/modeling/attribute.md b/versioned_docs/version-3.x/modeling/attribute.md new file mode 100644 index 00000000..7994aff6 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/attribute.md @@ -0,0 +1,47 @@ +--- +sidebar_position: 4 +description: Attributes in ZModel +--- + +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Attribute + +Attributes allow you to attach metadata to models and fields. As you've seen in the previous sections, they are used for many purposes, like adding unique constraints, mapping names, etc. Attributes are also indispensable for modeling relations between models. + +## Naming conventions + +By convention, attributes attached to models use a double `@@` prefix, while those for fields use a single `@` prefix. + +```zmodel +model User { + id Int @id + email String @unique + + @@index([email, name]) +} +``` + +## Defining and applying attributes + + +Prisma schema doesn't allow users to define custom attributes, while ZModel allows it and uses it as a key mechanism for extensibility. + + +ZModel comes with a rich set of attributes that you can use directly. See [ZModel Language Reference](../reference/zmodel-language.md) for a complete list. You can also define your own custom attributes for specific purposes. Attributes are defined with a list of typed parameters. Parameters can named (default) or positional. Positional parameters can be passed with or without an explicit name. Parameters can also be optional. + +Here's an example for how the `@unique` attribute is defined: + +```zmodel +attribute @unique(map: String?, length: Int?, sort: SortOrder?) +``` + +You can apply it in various ways: + +```zmodel +model Foo { + x String @unique() // default application + y String @unique('y_unique') // positional parameter + z String @unique(map: 'z_unique', length: 10) // named parameter +} +``` diff --git a/versioned_docs/version-3.x/modeling/conclusion.md b/versioned_docs/version-3.x/modeling/conclusion.md new file mode 100644 index 00000000..4c5cc632 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/conclusion.md @@ -0,0 +1,17 @@ +--- +sidebar_position: 100 +description: Data modeling conclusion +--- + +# Conclusion + +Congratulations! You have learned all the essentials of data model with ZenStack. What's next? + +In the following parts, you'll learn how to put the schema into use and let it drive many aspects of your application, including: + +- [Creating an ORM to query the database](../orm/) +- [Evolving database schema with migrations](../migration/) +- [Exposing a data API with Query-as-a-Service](../service/) +- [Driving other useful utilities](../service/) + +Let's continue our journey with ZenStack! diff --git a/versioned_docs/version-3.x/modeling/custom-type.md b/versioned_docs/version-3.x/modeling/custom-type.md new file mode 100644 index 00000000..46f966ea --- /dev/null +++ b/versioned_docs/version-3.x/modeling/custom-type.md @@ -0,0 +1,46 @@ +--- +sidebar_position: 7 +description: Custom types in ZModel +--- + +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Custom Type + + +Custom type is a ZModel concept and doesn't exist in PSL. + + +Besides models, you can also define custom types to encapsulate complex data structures. The main difference between a model and a custom type is that the latter is not backed by a database table. + +Here's a simple example: + +```zmodel +type Address { + street String + city String + country String + zip Int +} +``` + +Custom types are defined exactly like models, with the exception that they cannot contain fields that are relations to other models. They can, however, contain fields that are other custom types. + +There are two ways to use custom types: + +```zmodel +type Address { + street String + city String + country String + zip Int +} + +type UserProfile { + gender String + address Address? +} +``` + +- [Mixin](./mixin.md) +- [Strongly typed JSON fields](./strong-typed-json.md) diff --git a/versioned_docs/version-3.x/modeling/datasource.md b/versioned_docs/version-3.x/modeling/datasource.md new file mode 100644 index 00000000..6f308fff --- /dev/null +++ b/versioned_docs/version-3.x/modeling/datasource.md @@ -0,0 +1,46 @@ +--- +sidebar_position: 2 +description: Datasource in ZModel +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Data Source + +The `datasource` block provides information about the database your application uses. The ORM relies on it to determine the proper SQL dialect to use when generating queries. If you use [Migration](../migration/), it must also have a `url` field that specifies the database connection string, so that the migration engine knows how to connect to the database. The `env` function can be used to reference environment variables so you can keep sensitive information out of the code. + +:::tip +You can use both single quote and double quote for string literals. +::: + +Each ZModel schema must have exactly one `datasource` block. + + + + +```zmodel +datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') +} +``` + + + +```zmodel +datasource db { + provider = 'sqlite' + url = 'file:./dev.db' +} +``` + + + + +Currently only PostgreSQL and SQLite are supported. MySql will be supported in a future release. There's no plan for other relational database types or NoSQL databases. + + +ZenStack's ORM runtime doesn't rely on the `url` information to connect to the database. Instead, you provide the information when constructing an ORM client. More on this in the [ORM](../orm/) section. + diff --git a/versioned_docs/version-3.x/modeling/enum.md b/versioned_docs/version-3.x/modeling/enum.md new file mode 100644 index 00000000..f6410bba --- /dev/null +++ b/versioned_docs/version-3.x/modeling/enum.md @@ -0,0 +1,27 @@ +--- +sidebar_position: 4 +description: Enums in ZModel +--- + +# Enum + +Enums are simple constructs that allow you to define a set of named values. + +```zmodel +enum Role { + USER + ADMIN +} +``` + +They can be used to type model fields: + +```zmodel +model User { + id Int @id + // highlight-next-line + role Role @default(USER) +} +``` + +Enum field names are added to the global scope and are resolved without qualification. You need to make sure they don't collide with other global names. \ No newline at end of file diff --git a/versioned_docs/version-3.x/modeling/index.md b/versioned_docs/version-3.x/modeling/index.md new file mode 100644 index 00000000..95211884 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/index.md @@ -0,0 +1,52 @@ +--- +sidebar_position: 1 +description: ZModel overview +--- + +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Data Modeling Overview + +ZenStack uses a schema language named **ZModel** to define data models and their related aspects. We know that designing a good schema language is difficult, and we know it's even more difficult to convince people to learn a new one. So we made the decision to design ZModel as a superset of the [Prisma Schema Language (PSL)](https://www.prisma.io/docs/orm/prisma-schema), which is one of the best data modeling language out there. + +If you're already familiar with PSL, you'll find yourself at home with ZModel. However, we'd still recommend that you skim through this section to learn about the important extensions we made to PSL. Please pay attention to callouts like the following one: + + +ZModel uses the explicit `import` syntax for composing multi-file schemas. + + +Don't worry if you've never used Prisma before. This section will introduce all aspects of ZModel, so no prior knowledge is required. + +A very simple ZModel schema looks like this: + +```zmodel title='zenstack/schema.zmodel' +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String +} + +model Post { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + title String + content String + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId String +} +``` + + +Prisma has the concept of "generator" which provides a pluggable mechanism to generate artifacts from PSL. Specifically, you need to define a "prisma-client-js" (or "prisma-client") generator to get the ORM client. + +ZenStack CLI always generates a TypeScript schema without needing any configuration. Also, it replaced PSL's "generator" with a more generalized "plugin" construct that allows you to extend the system both at the schema level and the runtime level. Read more in the [Plugin](./plugin) section. + + +Let's dissect it piece by piece. diff --git a/versioned_docs/version-3.x/modeling/mixin.md b/versioned_docs/version-3.x/modeling/mixin.md new file mode 100644 index 00000000..c08b2b95 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/mixin.md @@ -0,0 +1,47 @@ +--- +sidebar_position: 8 +description: Reusing common fields with mixins +--- + +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Mixin + + +Mixin is a ZModel concept and don't exist in PSL. + + +:::info +Mixin was previously known as "abstract inheritance" in ZenStack v2. It's renamed and changed to use the `with` keyword to distinguish from polymorphic model inheritance. +::: + +Very often you'll find many of your models share quite a few common fields. It's tedious and error-prone to repeat them. As a rescue, you can put those fields into custom types, and "mix-in" them into your models. + +***Before:*** + +```zmodel +model User { + id String @id + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + email String @unique +} +``` + +***After:*** + +```zmodel +type BaseFieldsMixin { + id String @id + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model User with BaseFieldsMixin { + email String @unique +} +``` + +A model can use multiple mixins as long as their field names don't conflict. + +Mixins don't exist at the database level. The fields defined in the mixin types are conceptually inlined into the models that use them. diff --git a/versioned_docs/version-3.x/modeling/model.md b/versioned_docs/version-3.x/modeling/model.md new file mode 100644 index 00000000..1af5b1a3 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/model.md @@ -0,0 +1,190 @@ +--- +sidebar_position: 3 +description: Models in ZModel +--- + +# Model + +The `model` construct is the core of ZModel. It defines the structure of your data and their relations. A model represents a domain entity, and is mapped to a database table. + +## Defining models + +A typical model looks like this: + +```zmodel +model User { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + email String @unique + name String +} +``` + +The simplest models are just a collection of fields. A model must be uniquely identifiable by some of its fields. For most cases, you'll have a field marked with the `@id` attribute (more about [attributes](./attribute) later). + +```zmodel +model User { + // highlight-next-line + id Int @id +} +``` + +If your model needs a composite id, you can use the `@@id` model-level attribute to specify it: + +```zmodel +model City { + country String + name String + // highlight-next-line + @@id([country, name]) +} +``` + +If no `@id` or `@@id` is specified, the ORM will resort to using a field (or fields) marked with the `@unique` or `@@unique` attribute as the identifier. + +```zmodel +model User { + // highlight-next-line + email String @unique +} + +model City { + country String + name String + // highlight-next-line + @@unique([country, name]) +} +``` + +## Model fields + +Each model field must at least have a name and a type. A field can be typed in one of the following ways: + +1. Built-in types, including: + - String + - Boolean + - Int + - BigInt + - Float + - Decimal + - DateTime + - Json + - Bytes + - Unsupported + + The `Unsupported` type is for defining fields of types not supported by the ORM, however letting the migration engine know how to create the field in the database. + + ```zmodel + // from Prisma docs + model Star { + id Int @id + // highlight-next-line + position Unsupported("circle")? @default(dbgenerated("'<(10,4),11>'::circle")) + } + ``` + +2. Enum + + We'll talk about [enums](./enum) later. + + ```zmodel + enum Role { + USER + ADMIN + } + + model User { + id Int @id + // highlight-next-line + role Role + } + ``` + +3. Model + + It'll then form a relation. We'll cover that topic [later](./relation). + ```zmodel + model Post { + id Int @id + // highlight-next-line + author User @relation(fields: [authorId], references: [id]) + authorId Int + } + ``` +4. Custom type + + ZenStack allows you to define custom types in the schema and use that to type Json fields. This will be covered in more details in the [Custom Types](./custom-type) section. + + ```zmodel + type Address { + street String + city String + country String + zip Int + } + + model User { + id Int @id + // highlight-next-line + address Address @json + } + ``` + +A field can be set optional by adding the `?` suffix to its type, or list by adding the `[]` suffix. However, a field cannot be both optional and a list at the same time. + +```zmodel +model User { + id Int @id + // highlight-next-line + name String? + // highlight-next-line + tags String[] +} +``` + +A default value can be specified for a field with the `@default` attribute. The value can be a literal, or a supported function call, including: + +- `now()`: returns the current timestamp +- `cuid()`: returns a CUID +- `uuid()`: returns a UUID +- `ulid()`: returns a ULID +- `nanoid()`: returns a Nano ID +- `autoincrement()`: returns an auto-incrementing integer (only for integer fields) +- `dbgenerated("...")`: calls a native db function + +```zmodel +model User { + id Int @id @default(autoincrement()) + role Role @default("USER") + createdAt DateTime @default(now()) +} +``` + +## Native type mapping + +Besides giving field a type, you can also specify the native database type to use with the `@db.` series of attributes. + +```zmodel +model User { + ... + // highlight-next-line + name String @db.VarChar(64) +} +``` + +These attributes control what data type is used when the migration engine maps the schema to DDL. You can find a full list of native type attributes in the [ZModel Language Reference](../reference/zmodel-language). + +## Name mapping + +Quite often you want to use a different naming scheme for your models and fields than the database. You can achieve that with the `@map` attribute. The ORM respects the mapping when generation queries, and the migration engine uses it to generate the DDL. + +```zmodel +model User { + // highlight-next-line + id Int @id + name String @map("full_name") + + @@map("users") +} +``` diff --git a/versioned_docs/version-3.x/modeling/multi-file.md b/versioned_docs/version-3.x/modeling/multi-file.md new file mode 100644 index 00000000..75aaeb26 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/multi-file.md @@ -0,0 +1,42 @@ +--- +sidebar_position: 11 +description: Breaking down complex schemas into multiple files +--- + +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Multi-file Schema + + +Prisma uses an implicit approach that simply merges all schema files in a folder. ZModel uses explicit `import` syntax for better clarity and flexibility. + + +When your schema grows large, you can break them down to smaller files and stitch them together using the `import` statement. + +```zmodel title="zenstack/user.zmodel" +import './post' + +model User { + id Int @id + posts Post[] +} +``` + +```zmodel title="zenstack/post.zmodel" +import './user' + +model Post { + id Int @id + content String + author User @relation(fields: [authorId], references: [id]) + authorId Int +} +``` + +```zmodel title="zenstack/schema.zmodel" +import './user' +import './post' +``` + +After type-checking, these files are merged into a single schema AST before passed to the downstream tools. + diff --git a/versioned_docs/version-3.x/modeling/plugin.md b/versioned_docs/version-3.x/modeling/plugin.md new file mode 100644 index 00000000..25af5443 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/plugin.md @@ -0,0 +1,38 @@ +--- +sidebar_position: 11 +description: ZenStack plugins +--- + +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Plugin + + +ZenStack's "plugin" concept replaces PSL's "generator". + + +Plugin is a powerful mechanism that allows you to extend ZenStack at the schema, CLI, and runtime levels. This section only focuses on how to add plugins to your ZModel. Please refer to the [Plugin Development](../reference/plugin-dev.md) section for more details on how to develop plugins. + +## Adding plugins to ZModel + +Let's take a look at the following example: + +```zmodel +plugin myPlugin { + provider = 'my-zenstack-plugin' + output = './generated' +} +``` + +A plugin declaration involves three parts: + +1. A unique name +2. A `provider` field that specifies where to load the plugin from. It can be a built-in plugin (like `@core/prisma` here), a local folder, or an npm package. +3. Plugin-specific configuration options, such as `output` in this case. + +A plugin can have the following effects to ZModel: + +- It can contribute custom attributes that you can use to annotate models and fields. +- It can contribute code generation logic that's executed when you run the `zenstack generate` command. + +Plugins can also contribute to the ORM runtime behavior, and we'll leave it to the [ORM](../orm/plugins/) part to explain it in detail. diff --git a/versioned_docs/version-3.x/modeling/polymorphism.md b/versioned_docs/version-3.x/modeling/polymorphism.md new file mode 100644 index 00000000..87006ebe --- /dev/null +++ b/versioned_docs/version-3.x/modeling/polymorphism.md @@ -0,0 +1,122 @@ +--- +sidebar_position: 10 +description: Polymorphic models in ZModel +--- + +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Polymorphism + + +Polymorphism is a ZModel feature and doesn't exist in PSL. + + +## Introduction + +When modeling non-trivial applications, the need of an "Object-Oriented" kind of polymorphism often arises: +- Something **IS-A** more abstract type of thing. +- Something **HAS-A/HAS-many** a more abstract type of thing(s). + +Imagine we're modeling a content library system where users own different types of content: posts, images, videos, etc. They share some common traits like name, creation date, owner, etc., but have different specific fields. + +It may be tempting to use mixins to share the common fields, however it's not an ideal solution because: + +- The `User` table will have relations to each of the content types. +- There's no efficient and clean way to query all content types together (e.g., all content owned by a user). +- Consequently, whenever you add a new content type, you'll need to modify the `User` model, and probably lots of query code too. + +A true solution involves having a in-database model of polymorphism, where we really have a `Content` table that serves as an intermediary between `User` and the concrete content types. This is what ZModel polymorphism is about. + +:::info +There are [two main ways](https://www.prisma.io/docs/orm/prisma-schema/data-model/table-inheritance) to model polymorphism in relational databases: single-table inheritance (STI) and multi-table inheritance (MTI, aka. delegate types). ZModel only supports MTI. +::: + +## Modeling polymorphism + +Modeling polymorphism in ZModel is similar to designing an OOP class hierarchy - you introduce a base model and then extend it with concrete ones. + +Here's how it looks for our content library example: + +```zmodel +model User { + id Int @id + contents Content[] +} + +model Content { + id Int @id + name String + createdAt DateTime @default(now()) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + // highlight-next-line + type String + + // highlight-next-line + @@delegate(type) +} + +model Post extends Content { + content String +} + +model Image extends Content { + data Bytes +} + +model Video extends Content { + url String +} +``` + +```mermaid +erDiagram + User { + id Int PK + } + Content { + id Int PK + name String + createdAt Date + ownerId Int FK + type String + } + User ||--o{ Content: owns + Post { + id Int PK + content String + } + Post ||--|| Content: delegates + Image { + id Int PK + data Bytes + } + Image ||--|| Content: delegates + Video { + id Int PK + url String + } + Video ||--|| Content: delegates +``` + +There are two special things about polymorphic base model: + +1. It must have a "discriminator" field that stores the concrete model type that it should "delegate" to. In the example above, the `type` field serves this purpose. It can be named anything you like, but must be of `String` or enum type. +2. It must have a `@@delegate` attribute. The attribute serves two purposes: it indicates that the model is a base model, and it designates the discriminator field with its parameter. + +You can also have a deep hierarchy involving multiple level of base models. Just need to make sure each base model has its own discriminator field and `@@delegate` attribute. Extending from multiple base models directly is not supported. + +## Migration behavior + +The migration engine takes care of mapping both the base model and the concrete ones to tables, and creates one-to-one relations between the base and each of its derivations. + +To simplify query and conserve space, the base and the concrete are assumed to share the same id values (this is guaranteed by the ORM when creating the records), and consequently, the concrete model's id field is also reused as the foreign key to the base model. So, for a `Post` record with id `1`, the base `Content` record also has id `1`. + +## ORM behavior + +The ORM hides the delegate complexities and provides a simple polymorphic view to the developers: + +1. Creating a concrete model record automatically creates the base model record with the same id and proper discriminator field. +2. Querying with the base model will return entities with concrete model fields. + +We'll revisit the topic in details in the [ORM](../orm/) part. diff --git a/versioned_docs/version-3.x/modeling/relation.md b/versioned_docs/version-3.x/modeling/relation.md new file mode 100644 index 00000000..6a74b365 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/relation.md @@ -0,0 +1,219 @@ +--- +sidebar_position: 5 +description: Relations in ZModel +--- + +# Relations + +Relation is a fundamental concept in relational databases. It connect models into a graph, and allows you to query interconnected data efficiently. In ZModel, relations are modeled using the `@relation` attribute. For most cases it involves one side of the relation defining a foreign key field that references the primary key of the other side. By convention, we call the model that defines the foreign key the "owner" side. + +## One-to-one relations + +A typical one-to-one relation looks like this: + +```zmodel +model User { + id Int @id + profile Profile? +} + +model Profile { + id Int @id + user User @relation(fields: [userId], references: [id]) + userId Int @unique +} +``` + +The `Profile` model holds the foreign key `userId` and is the owner of the relation. The pk-fk association is established by the `@relation` attribute, where the `fields` parameters specifies the foreign key field(s) and the `references` parameter specifies the primary key field(s) of the other side. + +In one-to-one relations, the "non-owner" side must declare the relation field as optional (here `User.profile`), because there's no way to guarantee a `User` row always has a corresponding `Profile` row at the database level. The owner side can be either optional or required. + +Relations can also be explicitly named, and it's useful to disambiguate relations when a model has multiple relations to the same model, or to control the constraint name generated by the migration engine. + +```zmodel +model User { + id Int @id + profile Profile? @relation('UserProfile') +} + +model Profile { + id Int @id + user User @relation('UserProfile', fields: [userId], references: [id]) + userId Int @unique +} +``` + +Please note that even though both sides of the relation now have the `@relation` attribute, only the owner side can have the `fields` and `references` parameters. + +If a relation involves a model with composite PK fields, the FK fields must match the PK fields' count and types, and the `fields` and `references` parameters must be specified with those field tuples with matching order. + +```zmodel +model User { + id1 Int + id2 Int + profile Profile? + + @@id([id1, id2]) +} + +model Profile { + id Int @id + user User @relation(fields: [userId1, userId2], references: [id1, id2]) + userId1 Int + userId2 Int +} +``` + +## One-to-many relations + +A typical one-to-many relation looks like this: + +```zmodel +model User { + id Int @id + posts Post[] +} + +model Post { + id Int @id + author User @relation(fields: [authorId], references: [id]) + authorId Int +} +``` + +It's modeled pretty much the same way as one-to-one relations, except that the "non-owner" side (here `User.posts`) is a of list of the other side's model type. + +## Many-to-many relations + +Many-to-many relations are modeled in the database through a join table - which forms a many-to-one relation with each of the two sides. + +In ZModel, there are two ways to model many-to-many relations: implicitly or explicitly. + +### Implicit many-to-many + +An implicit many-to-many relation simply defines both sides of the relation as lists of the other side's model type, without defining a join table explicitly. + +```zmodel +model User { + id Int @id + posts Post[] +} + +model Post { + id Int @id + editors User[] +} +``` + +Under the hood, the migration engine creates a join table named `_PostToUser` (model names are sorted alphabetically), and the ORM runtime transparently handles the join table for you. + +You can also name the join table explicitly by adding the `@relation` attribute to both sides: + +```zmodel +model User { + id Int @id + posts Post[] @relation('UserPosts') +} + +model Post { + id Int @id + editors User[] @relation('UserPosts') +} +``` + +### Explicit many-to-many + +Explicit many-to-many relations are nothing but a join table with foreign keys linking the two sides. + +```zmodel +model User { + id Int @id + posts UserPost[] +} + +model Post { + id Int @id + editors UserPost[] +} + +model UserPost { + userId Int + postId Int + user User @relation(fields: [userId], references: [id]) + post Post @relation(fields: [postId], references: [id]) + + @@id([userId, postId]) +} +``` + +Since the join table is explicitly defined, when using the ORM, you'll need to involve it in your queries with an extra level of nesting. + +## Self relations + +Self relations are cases where a model has a relation to itself. They can be one-to-one, one-to-many, or many-to-many. + +### One-to-one + +```zmodel +model Employee { + id Int @id + mentorId Int? @unique + mentor Employee? @relation('Mentorship', fields: [mentorId], references: [id]) + mentee Employee? @relation('Mentorship') +} +``` + +Quick notes: + +- Both sides of the relation are defined in the same model. +- Both relation fields need to have `@relation` attributes with matching names. +- One side (here `mentor`) has a foreign key field (`mentorId`) that references the primary key. +- The foreign key field is marked `@unique` to guarantee one-to-one. + +### One-to-many + +```zmodel +model Employee { + id Int @id + managerId Int + manager Employee @relation('Management', fields: [managerId], references: [id]) + subordinates Employee[] @relation('Management') +} +``` + +Quick notes: +- Both sides of the relation are defined in the same model. +- Both relation fields need to have `@relation` attributes with matching names. +- One side (here `manager`) has a foreign key field (`managerId`) that references the primary key. +- The owner side (`Employee.manager`) can be either optional or required based on your needs. + +### Many-to-many + +Defining an implicit many-to-many self relation is very straightforward. + +```zmodel +model Employee { + id Int @id + mentors Employee[] @relation('Mentorship') + mentees Employee[] @relation('Mentorship') +} +``` + +You can also define an explicit one by modeling the join table explicitly. + +```zmodel +model Employee { + id Int @id + mentors Mentorship[] @relation('Mentorship') + mentees Mentorship[] @relation('Mentorship') +} + +model Mentorship { + mentorId Int + menteeId Int + mentor Employee @relation('Mentorship', fields: [mentorId], references: [id]) + mentee Employee @relation('Mentorship', fields: [menteeId], references: [id]) + + @@id([mentorId, menteeId]) +} +``` \ No newline at end of file diff --git a/versioned_docs/version-3.x/modeling/strong-typed-json.md b/versioned_docs/version-3.x/modeling/strong-typed-json.md new file mode 100644 index 00000000..2426d926 --- /dev/null +++ b/versioned_docs/version-3.x/modeling/strong-typed-json.md @@ -0,0 +1,50 @@ +--- +sidebar_position: 9 +description: Strongly typed JSON fields +--- + +import ZModelVsPSL from '../_components/ZModelVsPSL'; + +# Strongly Typed JSON + + +Strongly typed JSON is a ZModel feature and doesn't exist in PSL. + + +With relational databases providing better and better JSON support, their usage has become more common. However, in many cases your JSON fields still follow a specific structure, and when so, you can make the fields strongly typed so that: + +- When mutating the field, its structure is validated. +- When querying the field, its result is strongly typed. + +To type a JSON field, define a custom type in ZModel, use it as the field's type, and additionally mark the field with the `@json` attribute. + +***Before:*** + +```zmodel +model User { + id Int @id + address Json +} +``` + +***After:*** + +```zmodel +type Address { + street String + city String + country String + zip Int +} + +model User { + id Int @id + address Address @json +} +``` + +:::info +The `@json` attribute doesn't do anything except to clearly mark the field is a JSON field but not a relation to another model. +::: + +The migration engine still sees the field as a plain Json field. However, the ORM client enforces its structure and takes care of properly typing the query results. We'll revisit the topic in the [ORM](../orm/) part. diff --git a/versioned_docs/version-3.x/orm/_category_.yml b/versioned_docs/version-3.x/orm/_category_.yml new file mode 100644 index 00000000..6f69cf50 --- /dev/null +++ b/versioned_docs/version-3.x/orm/_category_.yml @@ -0,0 +1,4 @@ +position: 3 +label: ORM +collapsible: true +collapsed: true diff --git a/versioned_docs/version-3.x/orm/access-control/_category_.yml b/versioned_docs/version-3.x/orm/access-control/_category_.yml new file mode 100644 index 00000000..dcb120ba --- /dev/null +++ b/versioned_docs/version-3.x/orm/access-control/_category_.yml @@ -0,0 +1,4 @@ +position: 8 +label: Access Control +collapsible: true +collapsed: true diff --git a/versioned_docs/version-3.x/orm/access-control/introduction.md b/versioned_docs/version-3.x/orm/access-control/introduction.md new file mode 100644 index 00000000..261e992b --- /dev/null +++ b/versioned_docs/version-3.x/orm/access-control/introduction.md @@ -0,0 +1,3 @@ +# Introduction + +Coming soon 🚧 \ No newline at end of file diff --git a/versioned_docs/version-3.x/orm/api/_select-include-omit.md b/versioned_docs/version-3.x/orm/api/_select-include-omit.md new file mode 100644 index 00000000..5b57c5f4 --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/_select-include-omit.md @@ -0,0 +1,18 @@ +- `select` + + An object specifying the fields to include in the result. Setting a field to `true` means to include it. If a field is a relation, you can provide an nested object to further specify which fields of the relation to include. + + This field is optional. If not provided, all non-relation fields are included by default. The `include` field is mutually exclusive with the `select` field. + +- `include` + + An object specifying the relations to include in the result. Setting a relation to `true` means to include it. You can pass an object to further choose what fields/relations are included for the relation, and/or a `where` clause to filter the included relation records. + + This field is optional. If not provided, no relations are included by default. The `include` field is mutually exclusive with the `select` field. + +- `omit` + + An object specifying the fields to omit from the result. Setting a field to `true` means to omit it. Only applicable to non-relation fields. + + This field is optional. If not provided, no fields are omitted by default. The `omit` field is mutually exclusive with the `select` field. + \ No newline at end of file diff --git a/versioned_docs/version-3.x/orm/api/aggregate.md b/versioned_docs/version-3.x/orm/api/aggregate.md new file mode 100644 index 00000000..27b95a31 --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/aggregate.md @@ -0,0 +1,22 @@ +--- +sidebar_position: 7 +description: Aggregate API +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; + +# Aggregate + +The `aggregate` method allows you to conduct multiple aggregations on a set of records with one operation. The supported aggregations are: + +- `_count` - equivalent to the [Count API](./count.md). +- `_sum` - sum of a numeric field. +- `_avg` - average of a numeric field. +- `_min` - minimum value of a field. +- `_max` - maximum value of a field. + +You can also use `where`, `orderBy`, `skip`, and `take` to control what records are included in the aggregation. + +## Samples + + diff --git a/versioned_docs/version-3.x/orm/api/count.md b/versioned_docs/version-3.x/orm/api/count.md new file mode 100644 index 00000000..d337e632 --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/count.md @@ -0,0 +1,14 @@ +--- +sidebar_position: 6 +description: Count API +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; + +# Count + +You can use the `count` method to count the number of records that match a query. It also allows to count non-null field values with an `select` clause. + + + +To count relations, please use a `find` API with the special `_count` field as demonstrated in the [Find](./find.md#field-selection) section. diff --git a/versioned_docs/version-3.x/orm/api/create.md b/versioned_docs/version-3.x/orm/api/create.md new file mode 100644 index 00000000..74c8f447 --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/create.md @@ -0,0 +1,22 @@ +--- +sidebar_position: 2 +description: Create API +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; + +# Create + +The `create` series of APIs are used to create new records in the database. It has the following methods: + +- `create` + Create a single record, optionally with nested relations. +- `createMany` + Create multiple records in a single operation. Nested relations are not supported. Only the number of records created is returned. +- `createManyAndReturn` + Similar to `createMany`, but returns the created records. + +## Samples + + + diff --git a/versioned_docs/version-3.x/orm/api/delete.md b/versioned_docs/version-3.x/orm/api/delete.md new file mode 100644 index 00000000..e16e4dda --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/delete.md @@ -0,0 +1,20 @@ +--- +sidebar_position: 5 +description: Delete API +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; + +# Delete + +Deleting records can be done with the following methods: + +- `delete` - Delete a single, unique record. +- `deleteMany` - Delete multiple records that match the query criteria. +- `deleteManyAndReturn` - Similar to `deleteMany`, but returns the deleted records + +You can also delete records as part of an `update` operation from a relation. See [Manipulating relations](./update.md#manipulating-relations) for details. + +## Samples + + diff --git a/versioned_docs/version-3.x/orm/api/filter.md b/versioned_docs/version-3.x/orm/api/filter.md new file mode 100644 index 00000000..47fc7e6c --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/filter.md @@ -0,0 +1,85 @@ +--- +sidebar_position: 3 +description: how to filter entities +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; + +# Filter + +Filtering is an important topic because it's involved in many ORM operations, for example when you find records, selecting relations, and updating or deleting multiple records. + +## Basic filters + +You can filter on scalar fields with values or operators as supported by the field type. The following filter operators are available. + +- `equals` `not`: all scalar fields. +- `in` `notIn`: all scalar fields +- `contains` `startsWith` `endsWith`: String fields +- `lt` `lte` `gt` `gte`: String, Int, BigInt, Float, Decimal, and Date fields + +A filter object can contain multiple field filters, and they are combined with `AND` semantic. You can also use the `AND`, `OR`, and `NOT` logical operators to combine filter objects to form a complex filter. + + + +## Relation filters + +Filters can be defined on conditions over relations. For one-to-one relations, you can filter on their fields directly. For one-to-many relations, use the "some", "every", or "none" operators to build a condition over a list of records. + + + +## List filters + +List fields allow extra filter operators to filter on the list content: + +- `has`: checks if the list contains a specific value. +- `hasEvery`: checks if the list contains all values in a given array. +- `hasSome`: checks if the list contains at least one value in a given array. +- `isEmpty`: checks if the list is empty. + +:::info +List type is only supported for PostgreSQL. +::: + +```zmodel +model Post { + ... + topics String[] +} +``` + +```ts +await db.post.findMany({ + where: { topics: { has: 'webdev' } } +}); + +await db.post.findMany({ + where: { topics: { hasSome: ['webdev', 'typescript'] } } +}); + +await db.post.findMany({ + where: { topics: { hasEvery: ['webdev', 'typescript'] } } +}); + +await db.post.findMany({ + where: { topics: { hasNone: ['webdev', 'typescript'] } } +}); + +await db.post.findMany({ + where: { topics: { isEmpty: true } } +}); +``` + +## Json filters + +:::info WORK IN PROGRESS +Filtering on Json fields is work in progress and will be available soon. +::: + +## Query builder filters + +ZenStack v3 is implemented on top of [Kysely](https://kysely.dev/), and it leverages Kysely's powerful query builder API to extend the filtering capabilities. You can use the `$expr` operator to define a boolean expression that can express almost everything that can be expressed in SQL. + +The `$expr` operator can be used together with other filter operators, so you can keep most of your filters simple and only reach to the query builder level for complicated components. + + diff --git a/versioned_docs/version-3.x/orm/api/find.md b/versioned_docs/version-3.x/orm/api/find.md new file mode 100644 index 00000000..ce718521 --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/find.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 2 +description: Find API +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; +import SelectIncludeOmit from './_select-include-omit.md'; + +# Find + +The `find` series of APIs are used to query records from the database. It has the following methods: + +- `findMany` + + Find multiple records that match the query criteria. + +- `findUnique` + + Find a single record with a unique criteria. + +- `findFirst` + + Find the first record that matches the query criteria. + +- `findUniqueOrThrow` + + Similar to `findUnique`, but throws an error if no record is found. + +- `findFirstOrThrow` + + Similar to `findFirst`, but throws an error if no record is found. + +## Basic usage + + + +## Filtering + +The API provides a very flexible set of filtering options. We've put it into a [dedicated section](./filter.md). + +## Sorting + +Use the `sort` field to control the sort field, direction, and null field placement. Sorting is not supported for `findUnique` and `findUniqueOrThrow`. + + + +## Pagination + +You can use two strategies for pagination: offset-based or cursor-based. Pagination is not supported for `findUnique` and `findUniqueOrThrow`. + + + +## Field selection + +You can use the following fields to control what fields are returned in the result: + + + + + +## Finding distinct rows + +You can use the `distinct` field to find distinct rows based on specific fields. One row for each unique combination of the specified fields will be returned. The implementation uses SQL `DISTINCT ON` if it's supported by the dialect, otherwise falls back to in-memory deduplication. + + diff --git a/versioned_docs/version-3.x/orm/api/group-by.md b/versioned_docs/version-3.x/orm/api/group-by.md new file mode 100644 index 00000000..7ed21962 --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/group-by.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 8 +description: GroupBy API +--- + +# GroupBy \ No newline at end of file diff --git a/versioned_docs/version-3.x/orm/api/index.md b/versioned_docs/version-3.x/orm/api/index.md new file mode 100644 index 00000000..88eb3a66 --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/index.md @@ -0,0 +1,117 @@ +--- +sidebar_position: 1 +sidebar_label: Query API +title: Query API Overview +--- + +ZenStack ORM's query API provides a powerful and high-level way to interact with your database with awesome type safety. The API is a superset of [Prisma ORM's query API](https://www.prisma.io/docs/orm/prisma-client/queries), so if you are familiar with Prisma, you will feel right at home. If not, it's intuitive and easy to learn. + +The API is organized into several categories covered by the following sections. The API methods share many common input and output patterns, and we'll cover them in this overview section. + +## Common Input Fields + +- `where` + + When an operation can involve filtering records, a `where` clause is used to specify the condition. E.g., `findUnique`, `updateMany`, `delete`, etc. `where` clause also exists in nested payload for filtering relations. + + ```ts + await db.post.findMany({ where: { published: true } }); + ``` + + The [Filter](./filter) section describes the filtering capabilities in detail. + +- `select`, `include`, `omit` + + When an operation returns record(s), you can use these clauses to control the fields and relations returned in the result. The `select` clause is used to specify the fields/relations to return, `omit` to exclude, and `include` to include relations (together with all regular fields). + + When selecting relations, you can nest these clauses to further control fields and relations returned in the nested relations. + + ```ts + // results will include `title` field and `author` relation + await db.post.findMany({ + select: { title: true, author: true }, + }); + + // results will include all fields except `content`, plus `author` relation + await db.post.findMany({ + omit: { content: true }, include: { author: true } + }); + ``` + +- `orderBy`, `take`, `skip` + + When an operation returns multiple records, you can use these clauses to control the sort order, number of records returned, and the offset for pagination. + + ```ts + // results will be sorted by `createdAt` in descending order, and return + // 10 records starting from the 5th record + await db.post.findMany({ orderBy: { createdAt: 'desc' }, skip: 5, take: 10 }); + ``` + +- `data` + + When an operation involves creating or updating records, a `data` clause is used to specify the data to be used. It can include nested objects for manipulating relations. See the [Create](./create) and [Update](./update) sections for details. + + ```ts + // Create a new post and connect it to an author + await db.post.create({ + data: { title: 'New Post', author: { connect: { id: 1 } } } + }); + ``` + +## Output Types + +The output types of the API methods generally fall into three categories: + +1. When the operation returns record(s) + + The output type is "contextual" to the input's shape, meaning that when you specify `select`, `include`, or `omit` clauses, the output type will reflect that. + + ```ts + // result will be `Promise<{ title: string; author: { name: string } }[]>` + await db.post.findMany({ + select: { title: true, author: { select: { name: true } } } + }); + ``` + +2. When the operation returns a batch result + + Some operations only returns a batch result `{ count: number }`, indicating the number of records affected. These include `createMany`, `updateMany`, and `deleteMany`. + +3. Aggregation + + Aggregation operations' output type is contextual to the input's shape as well. See [Count](./count) and [Aggregate](./aggregate) sections for details. + + +## Sample Schema + +Throughout the following sections, we will use the following ZModel schema as the basis for our examples: + +```zmodel title="zenstack/schema.zmodel" +// This is a sample model to get you started. + +datasource db { + provider = 'sqlite' +} + +/// User model +model User { + id Int @id @default(autoincrement()) + email String @unique + posts Post[] +} + +/// Post model +model Post { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + title String + content String? + slug String? @unique + published Boolean @default(false) + viewCount Int @default(0) + author User? @relation(fields: [authorId], references: [id]) + authorId Int? +} +``` diff --git a/versioned_docs/version-3.x/orm/api/transaction.md b/versioned_docs/version-3.x/orm/api/transaction.md new file mode 100644 index 00000000..66bf2c9f --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/transaction.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 9 +description: Transaction API +--- + +# Transaction \ No newline at end of file diff --git a/versioned_docs/version-3.x/orm/api/update.md b/versioned_docs/version-3.x/orm/api/update.md new file mode 100644 index 00000000..9adcce77 --- /dev/null +++ b/versioned_docs/version-3.x/orm/api/update.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 4 +description: Update API +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; + +# Update + +Update to records can be done with the following methods: + +- `update` - Update a single, unique record. +- `updateMany` - Update multiple records that match the query criteria. +- `updateManyAndReturn` - Similar to `updateMany`, but returns the updated records +- `upsert` - Update a single, unique record, or create it if it does not exist. + +## Updating scalar fields + + + +In additional to the standard way of updating fields, list fields support the following operators: + +- `push`: Append a value or a list of values to the end of the list. +- `set`: Replace the entire list with a new list (equivalent to setting the field directly). + +```ts +await db.post.update({ + where: { id: '1' }, + data: { + topics: { push: 'webdev'}, + }, +}); + +await db.post.update({ + where: { id: '1' }, + data: { + topics: { set: ['orm', 'typescript'] }, + }, +}); +``` + +## Manipulating relations + +THe `update` and `upsert` methods are very powerful in that they allow you to freely manipulate relations. You can create, connect, disconnect, update, and delete relations in a single operation. You can also reach deeply into indirect relations. + +`updateMany` and `updateManyAndReturn` only support updating scalar fields. + + diff --git a/versioned_docs/version-3.x/orm/cli.md b/versioned_docs/version-3.x/orm/cli.md new file mode 100644 index 00000000..86de9ffd --- /dev/null +++ b/versioned_docs/version-3.x/orm/cli.md @@ -0,0 +1,22 @@ +--- +sidebar_position: 4 +description: Using the CLI +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; + +# Using the CLI + +ZenStack CLI is a command-line tool that takes the ZModel schema as input and complete different tasks for you. It's included in the "@zenstackhq/cli" package, and can be invoked with either `zen` or `zenstack` command. + +In the context of ORM, the CLI compiles ZModel into a TypeScript representation, which can in turn be used to create a type-safe ORM client. + +You can try running the `npx zen generate` command in the following playground and inspect the TypeScript code generated inside the "zenstack" folder. + + + +The `generate` command outputs the following TypeScript files in the same folder of the schema file: + +- `schema.ts`: TypeScript representation of the ZModel schema, used by the ORM client to understand the database's structure and infer types. +- `models.ts`: Exports types for all models, types, and enums defined in the schema. +- `input.ts`: Export types that you can use to type the arguments passed to the ORM client methods, such as `findMany`, `create`, etc. diff --git a/versioned_docs/version-3.x/orm/computed-fields.md b/versioned_docs/version-3.x/orm/computed-fields.md new file mode 100644 index 00000000..d6e2cbd7 --- /dev/null +++ b/versioned_docs/version-3.x/orm/computed-fields.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 10 +description: Computed fields in ZModel +--- + +# Computed Fields + diff --git a/versioned_docs/version-3.x/orm/database-client.md b/versioned_docs/version-3.x/orm/database-client.md new file mode 100644 index 00000000..166bafb7 --- /dev/null +++ b/versioned_docs/version-3.x/orm/database-client.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 5 +description: Creating a database client +--- + +import TabItem from '@theme/TabItem'; +import Tabs from '@theme/Tabs'; +import PackageInstall from '../_components/PackageInstall'; +import ZenStackVsPrisma from '../_components/ZenStackVsPrisma'; + +# Database Client + + +Unlike Prisma, ZenStack doesn't bundle any database driver. You're responsible for installing a compatible one. Also it doesn't read database connection string from the schema. Instead, you pass in the connection information when creating the client. + + +The `zen generate` command compiles the ZModel schema into TypeScript code, which we can in turn use to initialize a type-safe database client. ZenStack uses Kysely to handle the low-level database operations, so the client is initialize with a [Kysely dialect](https://kysely.dev/docs/dialects) - an object that encapsulates database details. + +The samples below only shows creating a client using SQLite (via [better-sqlite3](https://github.com/WiseLibs/better-sqlite3)) and PostgreSQL (via [node-postgres](https://github.com/brianc/node-postgres)), however you can also use any other Kysely dialects for these two types of databases. + + + + + + + +```ts title='db.ts' +import { ZenStackClient } from '@zenstackhq/runtime'; +import { SqliteDialect } from 'kysely'; +import SQLite from 'better-sqlite3'; +import { schema } from '@/zenstack/schema'; + +export const db = new ZenStackClient(schema, { + dialect: new SqliteDialect({ + database: new SQLite(':memory:'), + }), +}); +``` + + + + + + +```ts title='db.ts' +import { ZenStackClient } from '@zenstackhq/runtime'; +import { schema } from '@/zenstack/schema'; +import { PostgresDialect } from 'kysely'; +import { Pool } from 'pg'; + +export const db = new ZenStackClient(schema, { + dialect: new PostgresDialect({ + pool: new Pool({ + connectionString: process.env.DATABASE_URL, + }), + }), +}); +``` + + + + +The created `db` object has the full ORM API inferred from the type of the `schema` parameter. When necessary, you can also explicitly get the inferred client type like: + +```ts +import type { ClientContract } from '@zenstackhq/runtime'; +import type { SchemaType } from '@/zenstack/schema'; + +export type DbClient = ClientContract; +``` diff --git a/versioned_docs/version-3.x/orm/index.md b/versioned_docs/version-3.x/orm/index.md new file mode 100644 index 00000000..1096aad7 --- /dev/null +++ b/versioned_docs/version-3.x/orm/index.md @@ -0,0 +1,117 @@ +--- +sidebar_position: 1 +description: ZenStack ORM overview +--- + +import ZenStackVsPrisma from '../_components/ZenStackVsPrisma'; + +# Overview + +ZenStack ORM is a schema-first ORM for modern TypeScript applications. It learnt from the prior arts and strives to provide an awesome developer experience by combining the best ingredients into a cohesive package. + +## Key Features + +### [Prisma](https://prisma.io/orm)-compatible query API + +ZenStack v3 is inspired by Prisma ORM but it has a completely different implementation (based on [Kysely](https://kysely.dev/)). On the surface, it replicated Prisma ORM's query API so that you can use it pretty much as a drop-in replacement. Even if you're not a Prisma user, the query API is very intuitive and easy to learn. + +```ts +await db.user.findMany({ + where: { + email: { + endsWith: 'zenstack.dev', + }, + }, + orderBy: { + createdAt: 'desc', + }, + include: { posts: true } +}); +``` + +### Low-level query builder powered by [Kysely](https://kysely.dev/) + +ORM APIs are concise and pleasant, but they have their limitations. When you need extra power, you can fall back to the low-level query builder API powered by [Kysely](https://kysely.dev/) - limitless expressiveness, full type safety, and zero extra setup. + +```ts +await db.$qb + .selectFrom('User') + .leftJoin('Post', 'Post.authorId', 'User.id') + .select(['User.id', 'User.email', 'Post.title']) + .execute(); +``` + +### Access control + +ZenStack ORM comes with a powerful built-in access control system. You can define access rules right inside the schema. The rules are enforced at runtime via query injection, so it doesn't rely on any database specific row-level security features. + +```zmodel" +model Post { + id Int @id + title String @length(1, 256) + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId Int + + // no anonymous access + @@deny('all', auth() == null) + + // author has full access + @@allow('all', authorId == auth().id) + + // published posts are readable by anyone + @@allow('read', published) +} +``` + +### Polymorphic models + +Real-world applications often involves storing polymorphic data which is notoriously complex to model and query. ZenStack does the heavy-lifting for you so you can model an inheritance hierarchy with simple annotations, and query them with perfect type safety. + +```zmodel title="zenstack/schema.zmodel" +model Content { + id Int @id + name String @length(1, 256) + type String + + // the ORM uses the `type` field to determine to which concrete model + // a query should be delegated + @@delegate(type) +} + +model Post extends Content { + content String +} +``` + +```ts title="main.ts" +const asset = await db.asset.findFirst(); +if (asset.type === 'Post') { + // asset's type is narrowed down to `Post` + console.log(asset.content); +} else { + // other asset type +} +``` + +### Straightforward and light-Weighted + +Compared to Prisma and previous versions of ZenStack, v3 is more straightforward and light-weighted. + +- No runtime dependency to Prisma, thus no overhead of Rust/WASM query engines. +- No magic generating into `node_modules`. You fully control how the generated code is compiled and bundled. +- Less code generation, more type inference. + +## Documentation Conventions + +### Sample playground + +Throughout the documentation we'll use [StackBlitz](https://stackblitz.com/) to provide interactive code samples. StackBlitz's [WebContainers](https://webcontainers.io/) is an awesome technology that allows you to run a Node.js environment inside the browser. The embedded samples use the [sql.js](https://github.com/sql-js/sql.js) (a WASM implementation of SQLite) for WebContainers compatibility, which is not suitable for production use. + +### If you already know Prisma + +Although ZenStack ORM has a Prisma-compatible query API, the documentation doesn't assume prior knowledge of using Prisma. However, readers already familiar with Prisma can quickly skim through most of the content and focus on the differences. The documentation uses the following callout to indicate major differences between ZenStack ORM and Prisma: + + +Explanation of some key differences between ZenStack and Prisma ORM. + diff --git a/versioned_docs/version-3.x/orm/inferred-types.md b/versioned_docs/version-3.x/orm/inferred-types.md new file mode 100644 index 00000000..990d91d6 --- /dev/null +++ b/versioned_docs/version-3.x/orm/inferred-types.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 11 +description: TypeScript types derived from the ZModel schema +--- + +# Schema-Inferred Types diff --git a/versioned_docs/version-3.x/orm/plugins/_category_.yml b/versioned_docs/version-3.x/orm/plugins/_category_.yml new file mode 100644 index 00000000..12d5462a --- /dev/null +++ b/versioned_docs/version-3.x/orm/plugins/_category_.yml @@ -0,0 +1,4 @@ +position: 12 +label: Plugins +collapsible: true +collapsed: true diff --git a/versioned_docs/version-3.x/orm/plugins/index.md b/versioned_docs/version-3.x/orm/plugins/index.md new file mode 100644 index 00000000..8211aef1 --- /dev/null +++ b/versioned_docs/version-3.x/orm/plugins/index.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 1 +description: ORM plugin introduction +--- + +# Introduction to Plugins diff --git a/versioned_docs/version-3.x/orm/query-builder.md b/versioned_docs/version-3.x/orm/query-builder.md new file mode 100644 index 00000000..106b4ae4 --- /dev/null +++ b/versioned_docs/version-3.x/orm/query-builder.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 7 +description: Query builder API +--- + +# Query Builder API diff --git a/versioned_docs/version-3.x/orm/quick-start.md b/versioned_docs/version-3.x/orm/quick-start.md new file mode 100644 index 00000000..aa91ee71 --- /dev/null +++ b/versioned_docs/version-3.x/orm/quick-start.md @@ -0,0 +1,67 @@ +--- +sidebar_position: 2 +description: Quick start guide +--- + +import StackBlitzGithubEmbed from '@site/src/components/StackBlitzGithubEmbed'; +import ZModelStarter from '../_components/_zmodel-starter.md'; +import PackageInstall from '../_components/PackageInstall.tsx'; +import PackageExec from '../_components/PackageExec.tsx'; + +# Quick Start + +:::info +All v3 packages are currently published under the "@next" tag. +::: + +There are several ways to start using ZenStack ORM. + +## 1. Creating a project from scratch + +Run the following command to scaffold a new project with a pre-configured minimal starter: + +```bash +npm create zenstack@next my-project +``` + +Or simply use the following playground to experience it inside the browser. + + + +## 2. Adding to an existing project + +To add ZenStack to an existing project, run the CLI `init` command to install dependencies and create a sample schema: + + + +Then create a `zenstack/schema.zmodel` file in the root of your project. You can use the following sample schema to get started: + + + +Finally, run `zen generate` to compile the schema into TypeScript. + + + +## 3. Manual setup + +You can also always configure a project manually with the following steps: + +1. Install dependencies + + + +2. Create a `zenstack/schema.zmodel` file + + You can use the following sample schema to get started: + + + +3. Run the CLI `generate` command to compile the schema into TypeScript + + + +## 4. Custom schema and output paths + +By default, ZenStack CLI loads the schema from `zenstack/schema.zmodel`. You can change this by passing the `--schema` option. TypeScript files are by default generated to the same directory as the schema file. You can change this by passing the `--output` option. + +You can choose to either commit the generated TypeScript files to your source control, or add them to `.gitignore` and generate them on the fly in your CI/CD pipeline. diff --git a/versioned_docs/version-3.x/orm/validation.md b/versioned_docs/version-3.x/orm/validation.md new file mode 100644 index 00000000..7d0fb918 --- /dev/null +++ b/versioned_docs/version-3.x/orm/validation.md @@ -0,0 +1,8 @@ +--- +sidebar_position: 9 +description: Data validation in ZModel +--- + +# Data Validation + + diff --git a/versioned_docs/version-3.x/recipes/_category_.yml b/versioned_docs/version-3.x/recipes/_category_.yml new file mode 100644 index 00000000..acb7bc3f --- /dev/null +++ b/versioned_docs/version-3.x/recipes/_category_.yml @@ -0,0 +1,7 @@ +position: 5 +label: Recipes +collapsible: true +collapsed: true +link: + type: generated-index + title: Recipes diff --git a/versioned_docs/version-3.x/reference/_category_.yml b/versioned_docs/version-3.x/reference/_category_.yml new file mode 100644 index 00000000..1352c105 --- /dev/null +++ b/versioned_docs/version-3.x/reference/_category_.yml @@ -0,0 +1,7 @@ +position: 7 +label: Reference +collapsible: true +collapsed: true +link: + type: generated-index + title: Reference diff --git a/versioned_docs/version-3.x/reference/api.md b/versioned_docs/version-3.x/reference/api.md new file mode 100644 index 00000000..52b981ba --- /dev/null +++ b/versioned_docs/version-3.x/reference/api.md @@ -0,0 +1,7 @@ +--- +description: API references +sidebar_position: 4 +sidebar_label: API +--- + +# API Reference diff --git a/versioned_docs/version-3.x/reference/cli.md b/versioned_docs/version-3.x/reference/cli.md new file mode 100644 index 00000000..e6188f8b --- /dev/null +++ b/versioned_docs/version-3.x/reference/cli.md @@ -0,0 +1,251 @@ +--- +description: CLI references +sidebar_position: 2 +sidebar_label: CLI +--- + +# ZenStack CLI Reference + +## Usage + +``` +zenstack [options] [command] + +ΞΆ ZenStack is a Prisma power pack for building full-stack apps. + +Documentation: https://zenstack.dev. + +Options: + -v --version display CLI version + -h, --help display help for command + +Commands: + info [path] Get information of installed ZenStack and related packages. + init [options] [path] Initialize an existing project for ZenStack. + generate [options] Generates RESTful API and Typescript client for your data model. + repl [options] Start a REPL session. + format [options] Format a ZenStack schema file. + check [options] Check a ZenStack schema file for syntax or semantic errors. + help [command] Display help for a command. +``` + +## Sub Commands + +### info + +Get information of installed ZenStack and related packages. + +```bash +zenstack info [options] [path] +``` + +#### Arguments + +| Name | Description | Default | +| ---- | ------------ | -------------- | +| path | Project path | current folder | + +### init + +Initializes an existing project to use ZenStack. + +```bash +zenstack init [options] [path] +``` + +#### Arguments + +| Name | Description | Default | +| ---- | ------------ | -------------- | +| path | Project path | current folder | + +#### Options + +| Name | Description | Default | +| --------------------- | ------------------------------------------------ | ----------------------------------------- | +| --prisma | location of Prisma schema file to bootstrap from | <project path>/prisma/schema.prisma | +| -p, --package-manager | package manager to use: "npm", "yarn", or "pnpm" | auto detect | +| --no-version-check | do not check for new versions of ZenStack | false | + +#### Examples + +Initialize current folder with default settings. + +```bash +npx zenstack init +``` + +Initialize "myapp" folder with custom package manager and schema location. + +```bash +npx zenstack init -p pnpm --prisma prisma/my.schema myapp +``` + +### generate + +Generates Prisma schema and other artifacts as specified by "plugin"s in ZModel. + +```bash +zenstack generate [options] +``` + +#### Arguments + +| Name | Description | Default | +| ---- | ------------ | -------------- | +| path | Project path | current folder | + +#### Options + +| Name | Description | Default | +| --------------------- | ------------------------------------------------ | ---------------------- | +| --schema | schema file (with extension .zmodel) | ./schema.zmodel | +| -o, --output <path> | default output directory for TS/JS files generated by built-in plugins | node_modules/.zenstack | +| --with-plugins | only run specific plugins | | +| --without-plugins | exclude specific plugins | | +| --no-default-plugins | do not automatically run built-in plugins | false | +| --no-compile | do not compile the output of built-in plugins | false | +| --no-version-check | do not check for new versions of ZenStack | false | + +You can also specify the ZModel schema location in the "package.json" file of your project like the following: + +```json title="package.json" +{ + "zenstack": { + "schema": "./db/schema.zmodel" + } +} +``` + +#### Examples + +Generate with default settings. + +```bash +npx zenstack generate +``` + +Generate with custom schema location. + +```bash +npx zenstack generate --schema src/my.zmodel +``` + +### repl + +Starts a REPL session. You should run the command inside the package where you ran `zenstack generate`. + +```bash +npx zenstack repl +``` + +You can call PrismaClient methods interactively in the REPL session. The following variables are available in the REPL session. + +- `prisma` + + The original PrismaClient instance (without ZenStack enhancement). + +- `db` + + The ZenStack enhanced PrismaClient instance. + +You don't need to `await` the Prisma method call result. The REPL session will automatically await and print the result. + +#### Options + +| Name | Description | Default | +| ----- | ------------------- | ------- | +| --debug | Enable debug output. Can be toggled on the fly in the repl session with the ".debug" command. | false | +| --table | Enable table format. Can be toggled on the fly in the repl session with the ".table" command. | false | +| --prisma-client <path> | Path to load PrismaClient module. | "node_modules/.prisma/client" | +| --load-path <path> | Path to load modules generated by ZenStack. | "node_modules/.zenstack" | + +#### Repl Commands + +You can use the following commands in the REPL session. + +- `.debug [on|off]` + + Toggle debug output. + +- `.table [on|off]` + + Toggle table format. + +- `.auth [user object]` + + Set current user. E.g.: `.auth { id: 1 }`. Run the command without argument to reset to anonymous user. + +#### Examples + +Start the session: +```bash +npx zenstack repl +``` + +Inside the session: +```ts +> prisma.user.findMany() +[ + { + id: '7aa301d2-7a29-4e1e-a041-822913a3ea78', + createdAt: 2023-09-05T04:04:43.793Z, + updatedAt: 2023-09-05T04:04:43.793Z, + email: 'yiming@whimslab.io', + ... + } +] + +> .auth { id: '7aa301d2-7a29-4e1e-a041-822913a3ea78' } +Auth user: { id: '7aa301d2-7a29-4e1e-a041-822913a3ea78' }. Use ".auth" to switch to anonymous. + +> .table +Table output: true + +> db.list.findMany({select: { title: true, private: true}}) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ (index) β”‚ title β”‚ private β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 0 β”‚ 'L1' β”‚ false β”‚ +β”‚ 1 β”‚ 'Wonderful new world' β”‚ false β”‚ +β”‚ 2 β”‚ 'Model for a space in which users can collaborate' β”‚ false β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### format + +Format a ZenStack schema file. + +```bash +zenstack format [options] +``` + +#### Options + +| Name | Description | Default | +| --------------------- | ------------------------------------------------ | ---------------------- | +| --schema | schema file (with extension .zmodel) | ./schema.zmodel | + +You can also specify the ZModel schema location in the "package.json" file of your project like the following: + +```json title="package.json" +{ + "zenstack": { + "schema": "./db/schema.zmodel" + } +} +``` + +### check + +Check a ZenStack schema file for syntax or semantic errors. You can use the CLI's exit code to determine if the schema is valid. + +```bash +zenstack check [options] +``` + +#### Options + +| Name | Description | Default | +| --------------------- | ------------------------------------------------ | ---------------------- | +| --schema | schema file (with extension .zmodel) | ./schema.zmodel | diff --git a/versioned_docs/version-3.x/reference/plugin-dev.md b/versioned_docs/version-3.x/reference/plugin-dev.md new file mode 100644 index 00000000..5cd7ddef --- /dev/null +++ b/versioned_docs/version-3.x/reference/plugin-dev.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 5 +sidebar_label: Plugin Development +description: Plugin development guide +--- + +# Plugin Development diff --git a/versioned_docs/version-3.x/reference/plugins/_category_.yml b/versioned_docs/version-3.x/reference/plugins/_category_.yml new file mode 100644 index 00000000..4a2a7dc0 --- /dev/null +++ b/versioned_docs/version-3.x/reference/plugins/_category_.yml @@ -0,0 +1,7 @@ +position: 6 +label: Plugins +collapsible: true +collapsed: true +link: + type: generated-index + title: Plugins diff --git a/versioned_docs/version-3.x/reference/zmodel-language.md b/versioned_docs/version-3.x/reference/zmodel-language.md new file mode 100644 index 00000000..8b540c21 --- /dev/null +++ b/versioned_docs/version-3.x/reference/zmodel-language.md @@ -0,0 +1,8 @@ +--- +description: ZModel language references +sidebar_position: 1 +sidebar_label: ZModel Language +toc_max_heading_level: 3 +--- + +# ZModel Language Reference diff --git a/versioned_docs/version-3.x/samples.md b/versioned_docs/version-3.x/samples.md new file mode 100644 index 00000000..3e385d1b --- /dev/null +++ b/versioned_docs/version-3.x/samples.md @@ -0,0 +1,8 @@ +--- +sidebar_position: 8 +sidebar_label: Sample Projects +--- + +# A Catalog of Sample Projects + +The ZenStack team maintains the following three series of sample projects. diff --git a/versioned_docs/version-3.x/service/_category_.yml b/versioned_docs/version-3.x/service/_category_.yml new file mode 100644 index 00000000..938fc11d --- /dev/null +++ b/versioned_docs/version-3.x/service/_category_.yml @@ -0,0 +1,4 @@ +position: 5 +label: Query as a Service +collapsible: true +collapsed: true diff --git a/versioned_docs/version-3.x/service/index.md b/versioned_docs/version-3.x/service/index.md new file mode 100644 index 00000000..d97b5285 --- /dev/null +++ b/versioned_docs/version-3.x/service/index.md @@ -0,0 +1,3 @@ +# Overview + +Coming soon 🚧 \ No newline at end of file diff --git a/versioned_docs/version-3.x/upgrade.md b/versioned_docs/version-3.x/upgrade.md new file mode 100644 index 00000000..710d5964 --- /dev/null +++ b/versioned_docs/version-3.x/upgrade.md @@ -0,0 +1,10 @@ +--- +description: How to upgrade to ZenStack v3 + +slug: /upgrade-v3 +sidebar_label: Upgrading to V3 +sidebar_position: 9 +--- + +# Upgrading to V3 + diff --git a/versioned_docs/version-3.x/utilities/_category_.yml b/versioned_docs/version-3.x/utilities/_category_.yml new file mode 100644 index 00000000..1fdb7623 --- /dev/null +++ b/versioned_docs/version-3.x/utilities/_category_.yml @@ -0,0 +1,7 @@ +position: 6 +label: Utilities +collapsible: true +collapsed: true +link: + type: generated-index + title: Utilities diff --git a/versioned_docs/version-3.x/utilities/tanstack-query.md b/versioned_docs/version-3.x/utilities/tanstack-query.md new file mode 100644 index 00000000..fa0f1bda --- /dev/null +++ b/versioned_docs/version-3.x/utilities/tanstack-query.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 2 +description: TanStack Query integration +--- + +# TanStack Query diff --git a/versioned_docs/version-3.x/utilities/zod.md b/versioned_docs/version-3.x/utilities/zod.md new file mode 100644 index 00000000..7f16ec55 --- /dev/null +++ b/versioned_docs/version-3.x/utilities/zod.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 1 +description: Zod integration +--- + +# Zod diff --git a/versioned_docs/version-3.x/welcome.md b/versioned_docs/version-3.x/welcome.md new file mode 100644 index 00000000..29665f23 --- /dev/null +++ b/versioned_docs/version-3.x/welcome.md @@ -0,0 +1,28 @@ +--- +description: Welcome to ZenStack +slug: /welcome +sidebar_label: Welcome +sidebar_position: 1 +--- + +# Welcome + +Welcome to ZenStack - the modern data layer for your TypeScript application! + +ZenStack is built with the belief that most applications should use the data model as its center pillar. If that model is well-designed, it can serve as the single source of truth throughout the app's lifecycle, and be used to derive many other aspects of the app. The result is a smaller, more cohesive code base that scales well as your team grows while maintaining a high level of developer experience. + +Inside the package you'll find: + +- #### Intuitive schema language + That helps you model data, relation, access control, and more, in one place. [πŸ”—](./modeling/) + +- #### Powerful ORM + With awesomely-typed API, built-in access control, and unmatched flexibility. [πŸ”—](./orm/) + +- #### Query-as-a-Service + That provides a full-fledged data API without the need to code it up. [πŸ”—](./service/) + +- #### Utilities + For deriving artifacts like Zod schemas, frontend hooks, OpenAPI specs, etc., from the schema. [πŸ”—](./category/utilities) + +> *ZenStack originated as an extension to Prisma ORM. V3 is a complete rewrite that removed Prisma as a runtime dependency and replaced it with an implementation built from the scratch ("scratch" = [Kysely](https://kysely.dev/) πŸ˜†). On its surface, it continues to use a "Prisma-superset" schema language and a query API compatible with PrismaClient.* \ No newline at end of file diff --git a/versioned_sidebars/version-3.x-sidebars.json b/versioned_sidebars/version-3.x-sidebars.json new file mode 100644 index 00000000..fc4fe139 --- /dev/null +++ b/versioned_sidebars/version-3.x-sidebars.json @@ -0,0 +1,8 @@ +{ + "mySidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/versions.json b/versions.json index c339c072..bbcb99fe 100644 --- a/versions.json +++ b/versions.json @@ -1 +1 @@ -["1.x"] +["1.x", "3.x"]