From e2e97a36ef56f5fe04781525a61e169bab47f87e Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:53:40 +0800 Subject: [PATCH 1/6] doc: plugin development guide --- .gitmodules | 3 + code-repos/zenstackhq/v3-doc-plugin | 1 + versioned_docs/version-3.x/modeling/plugin.md | 4 +- versioned_docs/version-3.x/orm/cli.md | 4 + .../version-3.x/orm/plugins/index.md | 4 + .../version-3.x/recipe/plugin-dev.md | 107 ++++++++++++++++++ .../version-3.x/reference/plugin-dev.md | 8 -- 7 files changed, 122 insertions(+), 9 deletions(-) create mode 160000 code-repos/zenstackhq/v3-doc-plugin create mode 100644 versioned_docs/version-3.x/recipe/plugin-dev.md delete mode 100644 versioned_docs/version-3.x/reference/plugin-dev.md diff --git a/.gitmodules b/.gitmodules index 6baa5ba7..c865a60d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "code-repos/zenstackhq/v3-doc-tanstack-query"] path = code-repos/zenstackhq/v3-doc-tanstack-query url = https://github.com/zenstackhq/v3-doc-tanstack-query +[submodule "code-repos/zenstackhq/v3-doc-plugin"] + path = code-repos/zenstackhq/v3-doc-plugin + url = https://github.com/zenstackhq/v3-doc-plugin diff --git a/code-repos/zenstackhq/v3-doc-plugin b/code-repos/zenstackhq/v3-doc-plugin new file mode 160000 index 00000000..de196e1a --- /dev/null +++ b/code-repos/zenstackhq/v3-doc-plugin @@ -0,0 +1 @@ +Subproject commit de196e1a56459d7685b4ee68117482a4b2afdae1 diff --git a/versioned_docs/version-3.x/modeling/plugin.md b/versioned_docs/version-3.x/modeling/plugin.md index 0166b44a..80e737fc 100644 --- a/versioned_docs/version-3.x/modeling/plugin.md +++ b/versioned_docs/version-3.x/modeling/plugin.md @@ -48,4 +48,6 @@ 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. +:::info +Plugins can also extend ZenStack's CLI and ORM runtime behavior. Please refer to the [Plugin Development](../recipe/plugin-dev.md) documentation for a comprehensive guide. +::: diff --git a/versioned_docs/version-3.x/orm/cli.md b/versioned_docs/version-3.x/orm/cli.md index a6944b0b..29d81ffe 100644 --- a/versioned_docs/version-3.x/orm/cli.md +++ b/versioned_docs/version-3.x/orm/cli.md @@ -16,3 +16,7 @@ You can try running the `npx zen generate` command in the following playground a The `generate` command generates several TypeScript files from the ZModel schema that support both development-time typing and runtime access to the schema. For more details of the generated code, please refer to the [@core/typescript plugin](../reference/plugins/typescript.md) documentation. + +:::info +The CLI's code generation is extensible via plugins. Please refer to the [Plugin Development](../recipe/plugin-dev.md) documentation for a comprehensive guide. +::: diff --git a/versioned_docs/version-3.x/orm/plugins/index.md b/versioned_docs/version-3.x/orm/plugins/index.md index c2b299bb..3a960f11 100644 --- a/versioned_docs/version-3.x/orm/plugins/index.md +++ b/versioned_docs/version-3.x/orm/plugins/index.md @@ -40,3 +40,7 @@ const db = new ZenStackClient({ ... }); const withPlugin = $db.use({ ... }); const noPlugin = withPlugin.$unuseAll(); ``` + +:::info +Plugins can also extend the ZModel language and ZenStack's CLI. Please refer to the [Plugin Development](../../recipe/plugin-dev.md) documentation for a comprehensive guide. +::: diff --git a/versioned_docs/version-3.x/recipe/plugin-dev.md b/versioned_docs/version-3.x/recipe/plugin-dev.md new file mode 100644 index 00000000..2de8579f --- /dev/null +++ b/versioned_docs/version-3.x/recipe/plugin-dev.md @@ -0,0 +1,107 @@ +--- +sidebar_position: 5 +description: Plugin development guide +--- + +import StackBlitzGithub from '@site/src/components/StackBlitzGithub'; +import PackageInstall from '../_components/PackageInstall'; + +# Plugin Development + +This guide provides a comprehensive introduction to developing ZenStack plugins, demonstrating how to extend ZenStack’s functionality at the schema, CLI, and runtime levels. + +## Extending the schema language + +Plugins can contribute new ZModel attributes and functions, and use them to extend the data modeling semantics. To do that, create a folder in your source tree with a `plugin.zmodel` file in it, and define your custom attributes and/or functions in it. + +:::tip +You can also package a plugin as an npm package. Make sure to export the "plugin.zmodel" file in the package's `package.json` file. +::: + +The following example shows a sample plugin that allows you to mark fields as "password" and specify hashing algorithms to use. + + + +:::info +Model-level attributes must be prefixed with `@@`, and field-level ones with `@`. +::: + +You need to enable the plugin in your ZModel to use the attributes and functions defined in it: + +```zmodel title="schema.zmodel" +plugin password { + provider = './password-plugin' +} + +model User { + id Int @id @default(autoincrement()) + password String @password(hasher: bcryptHasher(10)) +} +``` + +## Generating custom artifacts + +The `zen` CLI is extensible via plugins and allows you to generate custom artifacts from the ZModel schema. To continue the previous example, let's create a very simple CLI plugin that generates a markdown document listing all model fields marked as passwords. + +To implement a plugin, first install the "@zenstackhq/sdk" package that contains type definitions and utilities for working with ZModel AST: + + + +A CLI plugin is an ESM module that default-exports an object that contains the following fields: + +- `name`: the name of the plugin. +- `generate`: an async function that's invoked during the `zen generate` command run. +- `statusText` (optional): text displayed in the CLI during the plugin run. + +The implementation looks like the following: + + + +Then, enable the reporting in ZModel with the "report" option: + +```zmodel title="schema.zmodel" +plugin password { + provider = './password-plugin' + report = true +} +``` + +Finally, run the `zen generate` command to generate the report: + +```bash +npx zen generate +``` + +You should see that the custom plugin is run during the generation process, and the markdown file is created in the output folder. + +```plain +% npx zen generate +✔ Generating TypeScript schema +✔ Running plugin Password Report +Generation completed successfully in 116ms. +``` + +:::info How does the CLI load plugin modules? +The CLI attempts to load the plugin module following these steps: +1. If `provider` is resolvable as a file path (with ".js", ".mjs", ".ts", or ".mts" extensions), load it as a local file. +2. If `provider` is resolvable as a folder containing an index file (with ".js", ".mjs", ".ts", or ".mts" extensions), load the index file. +3. Otherwise, load it as an npm package. + +Please note that only ESM modules are supported. TypeScript files are loaded via [jiti](https://gi.thub.com/unjs/jiti). +::: + +## Extending the ORM runtime + +The most powerful aspect of the plugin system is the ability to extend the ORM runtime behavior. ZenStack's ORM client provides a plugin system that allows you to intercept the ORM query lifecycle at different stages and abstraction levels. Please see the [ORM plugins](../orm/plugins/) documentation for detailed information. + +In this section, we'll see how to implement automatic password hashing functionality at runtime using the [Kysely](https://kysely.dev/) query hooks mechanism. The implementation requires an understanding of Kysely's [Operation Node](https://kysely-org.github.io/kysely-apidoc/interfaces/OperationNode.html) concept (which is the SQL AST used by Kysely internally). + +:::warning +The implementation is mostly vibe-coded with Claude Code. Please review carefully before using it in production. +::: + + + +With the plugin installed on the ORM client, any time you create or update a User record, the password field will be automatically hashed before being stored in the database. + + diff --git a/versioned_docs/version-3.x/reference/plugin-dev.md b/versioned_docs/version-3.x/reference/plugin-dev.md deleted file mode 100644 index 275fd325..00000000 --- a/versioned_docs/version-3.x/reference/plugin-dev.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -sidebar_position: 6 -description: Plugin development guide ---- - -# Plugin Development 🚧 - -Coming soon 🚧 From 788a42967c28c03e65c5ea706e08f3e716ef6bbd Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:54:18 +0800 Subject: [PATCH 2/6] update --- code-repos/zenstackhq/v3-doc-plugin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code-repos/zenstackhq/v3-doc-plugin b/code-repos/zenstackhq/v3-doc-plugin index de196e1a..7740cdf8 160000 --- a/code-repos/zenstackhq/v3-doc-plugin +++ b/code-repos/zenstackhq/v3-doc-plugin @@ -1 +1 @@ -Subproject commit de196e1a56459d7685b4ee68117482a4b2afdae1 +Subproject commit 7740cdf8d9a38db9085ff55563cd0834a1b8621a From e576098c0816f8535586eb851d1f62e38283f456 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:38:01 +0800 Subject: [PATCH 3/6] fix link --- versioned_docs/version-3.x/modeling/plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versioned_docs/version-3.x/modeling/plugin.md b/versioned_docs/version-3.x/modeling/plugin.md index 80e737fc..35f5fa27 100644 --- a/versioned_docs/version-3.x/modeling/plugin.md +++ b/versioned_docs/version-3.x/modeling/plugin.md @@ -11,7 +11,7 @@ import ZModelVsPSL from '../_components/ZModelVsPSL'; 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. +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](../recipe/plugin-dev.md) section for more details on how to develop plugins. ## Adding plugins to ZModel From cc356e83d114620e40ffe561bc692d6c7661a650 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 18 Dec 2025 21:43:21 +0800 Subject: [PATCH 4/6] update --- versioned_docs/version-3.x/recipe/plugin-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versioned_docs/version-3.x/recipe/plugin-dev.md b/versioned_docs/version-3.x/recipe/plugin-dev.md index 2de8579f..6ed0ee15 100644 --- a/versioned_docs/version-3.x/recipe/plugin-dev.md +++ b/versioned_docs/version-3.x/recipe/plugin-dev.md @@ -87,7 +87,7 @@ The CLI attempts to load the plugin module following these steps: 2. If `provider` is resolvable as a folder containing an index file (with ".js", ".mjs", ".ts", or ".mts" extensions), load the index file. 3. Otherwise, load it as an npm package. -Please note that only ESM modules are supported. TypeScript files are loaded via [jiti](https://gi.thub.com/unjs/jiti). +Please note that only ESM modules are supported. TypeScript files are loaded via [jiti](https://github.com/unjs/jiti). ::: ## Extending the ORM runtime From b50db1c9a36196beee64964992e95983c04e963e Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:01:18 +0800 Subject: [PATCH 5/6] update StackBlitz link --- code-repos/zenstackhq/v3-doc-orm | 2 +- .../zenstackhq/v3-doc-orm-computed-fields | 2 +- code-repos/zenstackhq/v3-doc-orm-policy | 2 +- code-repos/zenstackhq/v3-doc-orm-polymorphism | 2 +- code-repos/zenstackhq/v3-doc-orm-typed-json | 2 +- code-repos/zenstackhq/v3-doc-orm-validation | 2 +- code-repos/zenstackhq/v3-doc-plugin | 2 +- code-repos/zenstackhq/v3-doc-quick-start | 2 +- code-repos/zenstackhq/v3-doc-server-adapter | 2 +- code-repos/zenstackhq/v3-doc-tanstack-query | 2 +- package.json | 1 - pnpm-lock.yaml | 8 ------ src/components/StackBlitzEmbed.tsx | 26 ------------------- src/components/StackBlitzGithub.tsx | 17 +++++++----- 14 files changed, 20 insertions(+), 52 deletions(-) delete mode 100644 src/components/StackBlitzEmbed.tsx diff --git a/code-repos/zenstackhq/v3-doc-orm b/code-repos/zenstackhq/v3-doc-orm index d8777abf..69ae291b 160000 --- a/code-repos/zenstackhq/v3-doc-orm +++ b/code-repos/zenstackhq/v3-doc-orm @@ -1 +1 @@ -Subproject commit d8777abf227bffa300df12cac16889cbf8ad0aed +Subproject commit 69ae291b9734b5409e159957dac1c51ca7bca413 diff --git a/code-repos/zenstackhq/v3-doc-orm-computed-fields b/code-repos/zenstackhq/v3-doc-orm-computed-fields index 552d3eea..558b288a 160000 --- a/code-repos/zenstackhq/v3-doc-orm-computed-fields +++ b/code-repos/zenstackhq/v3-doc-orm-computed-fields @@ -1 +1 @@ -Subproject commit 552d3eea41a4e32a9039bd19781df290f100474c +Subproject commit 558b288ac512b1b8fbaf030226f51b3da565243d diff --git a/code-repos/zenstackhq/v3-doc-orm-policy b/code-repos/zenstackhq/v3-doc-orm-policy index ab1fedc5..e607c4fb 160000 --- a/code-repos/zenstackhq/v3-doc-orm-policy +++ b/code-repos/zenstackhq/v3-doc-orm-policy @@ -1 +1 @@ -Subproject commit ab1fedc5b57c1738dcf58a1b06163405565dc375 +Subproject commit e607c4fb16f945bebb80afa4448d81a2fe1651a9 diff --git a/code-repos/zenstackhq/v3-doc-orm-polymorphism b/code-repos/zenstackhq/v3-doc-orm-polymorphism index 3ef64e7d..c94e336d 160000 --- a/code-repos/zenstackhq/v3-doc-orm-polymorphism +++ b/code-repos/zenstackhq/v3-doc-orm-polymorphism @@ -1 +1 @@ -Subproject commit 3ef64e7d2a66d8bdfbf3c0ad379f4db9fea0aac1 +Subproject commit c94e336da542a5320ee1f1de8e66f9a4eda69d5c diff --git a/code-repos/zenstackhq/v3-doc-orm-typed-json b/code-repos/zenstackhq/v3-doc-orm-typed-json index 7245f9ac..f83b8425 160000 --- a/code-repos/zenstackhq/v3-doc-orm-typed-json +++ b/code-repos/zenstackhq/v3-doc-orm-typed-json @@ -1 +1 @@ -Subproject commit 7245f9acc2bb6e94acfb20d188e1a14b6fa288da +Subproject commit f83b8425f0d4194374b5c81d038f35dc314b037a diff --git a/code-repos/zenstackhq/v3-doc-orm-validation b/code-repos/zenstackhq/v3-doc-orm-validation index 20014efe..63a5e7d3 160000 --- a/code-repos/zenstackhq/v3-doc-orm-validation +++ b/code-repos/zenstackhq/v3-doc-orm-validation @@ -1 +1 @@ -Subproject commit 20014efeabbcb54685d017846bc17f9ab8427ea8 +Subproject commit 63a5e7d34b11174f1a1c2e433b209284075d115a diff --git a/code-repos/zenstackhq/v3-doc-plugin b/code-repos/zenstackhq/v3-doc-plugin index 7740cdf8..a7b72b95 160000 --- a/code-repos/zenstackhq/v3-doc-plugin +++ b/code-repos/zenstackhq/v3-doc-plugin @@ -1 +1 @@ -Subproject commit 7740cdf8d9a38db9085ff55563cd0834a1b8621a +Subproject commit a7b72b95840d3cbe8f3e9e158c3d7c278a65009f diff --git a/code-repos/zenstackhq/v3-doc-quick-start b/code-repos/zenstackhq/v3-doc-quick-start index 64d688f0..1adc1ce3 160000 --- a/code-repos/zenstackhq/v3-doc-quick-start +++ b/code-repos/zenstackhq/v3-doc-quick-start @@ -1 +1 @@ -Subproject commit 64d688f0b0908a5526e88eae5015a5615023fe3d +Subproject commit 1adc1ce34385ce8ea958b981a8e6f454b02afe32 diff --git a/code-repos/zenstackhq/v3-doc-server-adapter b/code-repos/zenstackhq/v3-doc-server-adapter index 4c71ba7b..1c4cdb68 160000 --- a/code-repos/zenstackhq/v3-doc-server-adapter +++ b/code-repos/zenstackhq/v3-doc-server-adapter @@ -1 +1 @@ -Subproject commit 4c71ba7bb78791633305a8287d2ef2016b0636ef +Subproject commit 1c4cdb6819f0267e7420accd6e9a6b2e923ef217 diff --git a/code-repos/zenstackhq/v3-doc-tanstack-query b/code-repos/zenstackhq/v3-doc-tanstack-query index 98a7a610..96b1d578 160000 --- a/code-repos/zenstackhq/v3-doc-tanstack-query +++ b/code-repos/zenstackhq/v3-doc-tanstack-query @@ -1 +1 @@ -Subproject commit 98a7a610c0f63718386363f798ad2018197d56ed +Subproject commit 96b1d578fb0ba9a12870b8122127840bc39ecbe4 diff --git a/package.json b/package.json index e881f2b9..5c9a86a8 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "@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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4fd5ed04..05df4e34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,6 @@ 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) @@ -1122,9 +1119,6 @@ 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'} @@ -6932,8 +6926,6 @@ 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 diff --git a/src/components/StackBlitzEmbed.tsx b/src/components/StackBlitzEmbed.tsx deleted file mode 100644 index db7b6418..00000000 --- a/src/components/StackBlitzEmbed.tsx +++ /dev/null @@ -1,26 +0,0 @@ -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/StackBlitzGithub.tsx b/src/components/StackBlitzGithub.tsx index a62de95c..f53c2492 100644 --- a/src/components/StackBlitzGithub.tsx +++ b/src/components/StackBlitzGithub.tsx @@ -1,4 +1,3 @@ -import sdk from '@stackblitz/sdk'; import React from 'react'; import GithubCodeBlock from './GithubCodeBlock'; @@ -17,11 +16,15 @@ const StackBlitzGithub: React.FC = ({ }) => { const openFiles = Array.isArray(openFile) ? openFile : openFile ? openFile.split(',') : []; - const options = { - openFile: openFiles ? openFiles.join(',') : undefined, - view: 'editor', - startScript, - } as const; + // construct StackBlitz URL + const url = new URL(`https://stackblitz.com/~/github/${repoPath}`); + url.searchParams.append('view', 'editor'); + if (openFiles && openFiles.length > 0) { + openFiles.forEach((f) => url.searchParams.append('file', f)); + } + if (startScript) { + url.searchParams.append('startScript', startScript); + } if (!plainCodeFiles) { plainCodeFiles = [...openFiles]; @@ -29,7 +32,7 @@ const StackBlitzGithub: React.FC = ({ return ( <> - sdk.openGithubProject(repoPath, options)}> + Open in StackBlitz {plainCodeFiles.map((file) => ( From 4584ac09ae85fca372171bf8b59ed51c594d23c0 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:08:06 +0800 Subject: [PATCH 6/6] update --- src/components/StackBlitzGithub.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/StackBlitzGithub.tsx b/src/components/StackBlitzGithub.tsx index f53c2492..a243747d 100644 --- a/src/components/StackBlitzGithub.tsx +++ b/src/components/StackBlitzGithub.tsx @@ -32,7 +32,7 @@ const StackBlitzGithub: React.FC = ({ return ( <> - + Open in StackBlitz {plainCodeFiles.map((file) => (