diff --git a/README.md b/README.md index 1e8bddc51..34b4b3ba1 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,119 @@ # Redwood Tutorial App -This repo represents the final state of the app created during the [Redwood Tutorial](https://redwoodjs.com/tutorial). -It is meant to be a starting point for those working on the second half of the Tutorial, starting at the [Intermission](https://redwoodjs.com/docs/tutorial/intermission). +This project demonstrates how to use [ZenStack](https://zenstack.dev) in a RedwoodJS project. It's extended based on the blog app used through [RedwoodJS's tutorial](https://redwoodjs.com/docs/tutorial/foreword). -This repo contains much more styling than the one we built together in the tutorial, but is functionally identical. +Please refer to [this blog post](https://zenstack.dev/blog/redwood-auth) for a general introduction. -## Setup +--- -The [tutorial itself](https://redwoodjs.com/docs/tutorial/chapter1/prerequisites) contains instructions for getting this repo up and running, but here is a summary of the commands: +## Steps to get started with ZenStack -```bash -git clone https://github.com/redwoodjs/redwood-tutorial -cd redwood-tutorial -yarn install -yarn rw prisma migrate dev -yarn rw dev -``` +1. Prepare project + + Install CLI + + ```bash + cd api + yarn add -D zenstack + yarn add @zenstackhq/runtime + ``` + + Bootstrap ZModel from `schema.prisma` + + ```bash + cp db/schema.prisma ./schema.zmodel + ``` + +1. Prepare model + + Add the following section to `schema.zmodel` to output the generated Prisma schema to the default location of Redwood: + + ``` + plugin prisma { + provider = '@core/prisma' + output = './db/schema.prisma' + } + ``` + + Run `zenstack generate` and verify that `db/schema.prisma` is updated. + + ```bash + yarn zenstack generate + ``` + +1. Add access policies + + Note the added `@@allow` rules (all operations are denied by default). + + ```prisma + model Post { + id Int @id @default(autoincrement()) + title String + body String + comments Comment[] + user User @relation(fields: [userId], references: [id]) + userId Int + createdAt DateTime @default(now()) + published Boolean @default(true) + + // 🔐 Admin user can do everything to his own posts + @@allow('all', auth().roles == 'admin' && auth() == user) + + // 🔐 Posts are visible to everyone if published + @@allow('read', published) + } + + model Comment { + id Int @id @default(autoincrement()) + name String + body String + post Post @relation(fields: [postId], references: [id]) + postId Int + createdAt DateTime @default(now()) + + // 🔐 Moderator user can do everything to comments + @@allow('all', auth().roles == 'moderator') + + // 🔐 Everyone is allowed to view and create comments for published posts + @@allow('create,read', post.published) + } + + ``` + + See the next section for where the `auth()` function's value comes from. + + Rerun generation and migrate the database. + + ```bash + yarn zenstack generate + yarn rw prisma migrate dev + ``` + +1. Create access-policy-enhanced Prisma Client + + Add the following function to `api/src/lib/db.js`: + + ```js + import { withPolicy } from '@zenstackhq/runtime' + + /* + * Returns ZenStack wrapped Prisma Client with access policies enabled. + */ + export function authDb() { + return withPolicy(db, { user: context.currentUser }) + } + ``` + + It uses the `withPolicy` API to create a Prisma Client wrapper (note the `context.currentUser` is passed in as the current user, which determines what the `auth()` function returns in the ZModel policy rules). + +1. Switch to relying on access policies for authorization + + For example, remove authorization from `api/src/services/comments.js` and use `authDb()` helper to access db. + + ```diff + export const deleteComment = ({ id }) => { + - requireAuth({ roles: 'moderator' }) + - return db.comment.delete({ where: { id } }) + return authDb().comment.delete({ where: { id } }) + } + ``` diff --git a/api/db/migrations/20230620053259_add_policies/migration.sql b/api/db/migrations/20230620053259_add_policies/migration.sql new file mode 100644 index 000000000..f0da32de4 --- /dev/null +++ b/api/db/migrations/20230620053259_add_policies/migration.sql @@ -0,0 +1,49 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_User" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT, + "email" TEXT NOT NULL, + "hashedPassword" TEXT NOT NULL, + "salt" TEXT NOT NULL, + "resetToken" TEXT, + "resetTokenExpiresAt" DATETIME, + "roles" TEXT NOT NULL DEFAULT 'moderator', + "zenstack_guard" BOOLEAN NOT NULL DEFAULT true, + "zenstack_transaction" TEXT +); +INSERT INTO "new_User" ("email", "hashedPassword", "id", "name", "resetToken", "resetTokenExpiresAt", "roles", "salt") SELECT "email", "hashedPassword", "id", "name", "resetToken", "resetTokenExpiresAt", "roles", "salt" FROM "User"; +DROP TABLE "User"; +ALTER TABLE "new_User" RENAME TO "User"; +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); +CREATE INDEX "User_zenstack_transaction_idx" ON "User"("zenstack_transaction"); +CREATE TABLE "new_Comment" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "body" TEXT NOT NULL, + "postId" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "zenstack_guard" BOOLEAN NOT NULL DEFAULT true, + "zenstack_transaction" TEXT, + CONSTRAINT "Comment_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_Comment" ("body", "createdAt", "id", "name", "postId") SELECT "body", "createdAt", "id", "name", "postId" FROM "Comment"; +DROP TABLE "Comment"; +ALTER TABLE "new_Comment" RENAME TO "Comment"; +CREATE INDEX "Comment_zenstack_transaction_idx" ON "Comment"("zenstack_transaction"); +CREATE TABLE "new_Post" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "title" TEXT NOT NULL, + "body" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "zenstack_guard" BOOLEAN NOT NULL DEFAULT true, + "zenstack_transaction" TEXT, + CONSTRAINT "Post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_Post" ("body", "createdAt", "id", "title", "userId") SELECT "body", "createdAt", "id", "title", "userId" FROM "Post"; +DROP TABLE "Post"; +ALTER TABLE "new_Post" RENAME TO "Post"; +CREATE INDEX "Post_zenstack_transaction_idx" ON "Post"("zenstack_transaction"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/api/db/migrations/20230622084334_add_published/migration.sql b/api/db/migrations/20230622084334_add_published/migration.sql new file mode 100644 index 000000000..5f6d98d82 --- /dev/null +++ b/api/db/migrations/20230622084334_add_published/migration.sql @@ -0,0 +1,19 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Post" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "title" TEXT NOT NULL, + "body" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "published" BOOLEAN NOT NULL DEFAULT true, + "zenstack_guard" BOOLEAN NOT NULL DEFAULT true, + "zenstack_transaction" TEXT, + CONSTRAINT "Post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_Post" ("body", "createdAt", "id", "title", "userId", "zenstack_guard", "zenstack_transaction") SELECT "body", "createdAt", "id", "title", "userId", "zenstack_guard", "zenstack_transaction" FROM "Post"; +DROP TABLE "Post"; +ALTER TABLE "new_Post" RENAME TO "Post"; +CREATE INDEX "Post_zenstack_transaction_idx" ON "Post"("zenstack_transaction"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/api/db/schema.prisma b/api/db/schema.prisma index 154619e09..0e176f067 100644 --- a/api/db/schema.prisma +++ b/api/db/schema.prisma @@ -1,48 +1,74 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + datasource db { - provider = "sqlite" - url = env("DATABASE_URL") + provider="sqlite" + url=env("DATABASE_URL") } generator client { - provider = "prisma-client-js" - binaryTargets = "native" + provider = "prisma-client-js" + binaryTargets = "native" + previewFeatures = ["interactiveTransactions"] } +/// @@allow('all', auth().roles == 'admin' && auth() == user) +/// @@allow('read', published) model Post { - id Int @id @default(autoincrement()) - title String - body String - comments Comment[] - user User @relation(fields: [userId], references: [id]) - userId Int - createdAt DateTime @default(now()) + id Int @id() @default(autoincrement()) + title String + body String + comments Comment[] + user User @relation(fields: [userId], references: [id]) + userId Int + createdAt DateTime @default(now()) + published Boolean @default(true) + + zenstack_guard Boolean @default(true) + zenstack_transaction String? + + @@index([zenstack_transaction]) } model Contact { - id Int @id @default(autoincrement()) - name String - email String - message String - createdAt DateTime @default(now()) + id Int @id() @default(autoincrement()) + name String + email String + message String + createdAt DateTime @default(now()) } model User { - id Int @id @default(autoincrement()) - name String? - email String @unique - hashedPassword String - salt String - resetToken String? - resetTokenExpiresAt DateTime? - roles String @default("moderator") - posts Post[] + id Int @id() @default(autoincrement()) + name String? + email String @unique() + hashedPassword String + salt String + resetToken String? + resetTokenExpiresAt DateTime? + roles String @default("moderator") + posts Post[] + + zenstack_guard Boolean @default(true) + zenstack_transaction String? + + @@index([zenstack_transaction]) } +/// @@allow('all', auth().roles == 'moderator') +/// @@allow('create,read', post.published) model Comment { - id Int @id @default(autoincrement()) - name String - body String - post Post @relation(fields: [postId], references: [id]) - postId Int - createdAt DateTime @default(now()) -} + id Int @id() @default(autoincrement()) + name String + body String + post Post @relation(fields: [postId], references: [id]) + postId Int + createdAt DateTime @default(now()) + + zenstack_guard Boolean @default(true) + zenstack_transaction String? + + @@index([zenstack_transaction]) +} \ No newline at end of file diff --git a/api/package.json b/api/package.json index 2edfead03..76b0f4613 100644 --- a/api/package.json +++ b/api/package.json @@ -4,6 +4,10 @@ "private": true, "dependencies": { "@redwoodjs/api": "3.2.0", - "@redwoodjs/graphql-server": "3.2.0" + "@redwoodjs/graphql-server": "3.2.0", + "@zenstackhq/runtime": "^1.0.0-beta.2" + }, + "devDependencies": { + "zenstack": "^1.0.0-beta.2" } } diff --git a/api/schema.zmodel b/api/schema.zmodel new file mode 100644 index 000000000..d8959267c --- /dev/null +++ b/api/schema.zmodel @@ -0,0 +1,67 @@ +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +plugin prisma { + provider = '@core/prisma' + output = './db/schema.prisma' +} + +generator client { + provider = "prisma-client-js" + binaryTargets = "native" + previewFeatures = ["interactiveTransactions"] +} + +model Post { + id Int @id @default(autoincrement()) + title String + body String + comments Comment[] + user User @relation(fields: [userId], references: [id]) + userId Int + createdAt DateTime @default(now()) + published Boolean @default(true) + + // 🔐 Admin user can do everything to his own posts + @@allow('all', auth().roles == 'admin' && auth() == user) + + // 🔐 Posts are visible to everyone if published + @@allow('read', published) +} + +model Contact { + id Int @id @default(autoincrement()) + name String + email String + message String + createdAt DateTime @default(now()) +} + +model User { + id Int @id @default(autoincrement()) + name String? + email String @unique + hashedPassword String + salt String + resetToken String? + resetTokenExpiresAt DateTime? + roles String @default("moderator") + posts Post[] +} + +model Comment { + id Int @id @default(autoincrement()) + name String + body String + post Post @relation(fields: [postId], references: [id]) + postId Int + createdAt DateTime @default(now()) + + // 🔐 Moderator user can do everything to comments + @@allow('all', auth().roles == 'moderator') + + // 🔐 Everyone is allowed to view and create comments for published posts + @@allow('create,read', post.published) +} diff --git a/api/src/graphql/adminPosts.sdl.js b/api/src/graphql/adminPosts.sdl.js index c4b49a6e4..0a49626e2 100644 --- a/api/src/graphql/adminPosts.sdl.js +++ b/api/src/graphql/adminPosts.sdl.js @@ -18,6 +18,8 @@ export const schema = gql` createPost(input: CreatePostInput!): Post! @requireAuth(roles: ["admin"]) updatePost(id: Int!, input: UpdatePostInput!): Post! @requireAuth(roles: ["admin"]) + publishPost(id: Int!): Post! @requireAuth(roles: ["admin"]) + unpublishPost(id: Int!): Post! @requireAuth(roles: ["admin"]) deletePost(id: Int!): Post! @requireAuth(roles: ["admin"]) } ` diff --git a/api/src/graphql/comments.sdl.js b/api/src/graphql/comments.sdl.js index 2dd153d03..156c64cd5 100644 --- a/api/src/graphql/comments.sdl.js +++ b/api/src/graphql/comments.sdl.js @@ -26,6 +26,6 @@ export const schema = gql` type Mutation { createComment(input: CreateCommentInput!): Comment! @skipAuth - deleteComment(id: Int!): Comment! @requireAuth(roles: "moderator") + deleteComment(id: Int!): Comment! @skipAuth } ` diff --git a/api/src/graphql/posts.sdl.js b/api/src/graphql/posts.sdl.js index 95b30ddf4..ae78e0f82 100644 --- a/api/src/graphql/posts.sdl.js +++ b/api/src/graphql/posts.sdl.js @@ -5,6 +5,7 @@ export const schema = gql` body: String! createdAt: DateTime! user: User! + published: Boolean! } type Query { diff --git a/api/src/lib/db.js b/api/src/lib/db.js index 5006d00aa..993a14913 100644 --- a/api/src/lib/db.js +++ b/api/src/lib/db.js @@ -2,6 +2,7 @@ // for options. import { PrismaClient } from '@prisma/client' +import { withPolicy } from '@zenstackhq/runtime' import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger' @@ -14,6 +15,14 @@ export const db = new PrismaClient({ log: emitLogLevels(['info', 'warn', 'error']), }) +/* + * Returns ZenStack wrapped Prisma Client with access policies enabled. + */ +export function authDb() { + console.log('Context User:', context.currentUser) + return withPolicy(db, { user: context.currentUser }) +} + handlePrismaLogging({ db, logger, diff --git a/api/src/services/adminPosts/adminPosts.js b/api/src/services/adminPosts/adminPosts.js index d409905e9..897ef4605 100644 --- a/api/src/services/adminPosts/adminPosts.js +++ b/api/src/services/adminPosts/adminPosts.js @@ -35,6 +35,28 @@ export const updatePost = async ({ id, input }) => { }) } +export const publishPost = async ({ id }) => { + await validateOwnership({ id }) + + return db.post.update({ + where: { id }, + data: { + published: true, + }, + }) +} + +export const unpublishPost = async ({ id }) => { + await validateOwnership({ id }) + + return db.post.update({ + where: { id }, + data: { + published: false, + }, + }) +} + export const deletePost = async ({ id }) => { await validateOwnership({ id }) diff --git a/api/src/services/comments/comments.js b/api/src/services/comments/comments.js index 7eface794..42345d118 100644 --- a/api/src/services/comments/comments.js +++ b/api/src/services/comments/comments.js @@ -1,31 +1,29 @@ -import { requireAuth } from 'src/lib/auth' -import { db } from 'src/lib/db' +import { authDb } from 'src/lib/db' export const comments = ({ postId }) => { - return db.comment.findMany({ where: { postId } }) + return authDb().comment.findMany({ where: { postId } }) } export const comment = ({ id }) => { - return db.comment.findUnique({ + return authDb().comment.findUnique({ where: { id }, }) } export const createComment = ({ input }) => { - return db.comment.create({ + return authDb().comment.create({ data: input, }) } export const deleteComment = ({ id }) => { - requireAuth({ roles: 'moderator' }) - return db.comment.delete({ + return authDb().comment.delete({ where: { id }, }) } export const Comment = { post: (_obj, { root }) => { - return db.comment.findUnique({ where: { id: root?.id } }).post() + return authDb().post.findUnique({ where: { id: root?.postId } }) }, } diff --git a/api/src/services/posts/posts.js b/api/src/services/posts/posts.js index b41b1d610..d7a880fd7 100644 --- a/api/src/services/posts/posts.js +++ b/api/src/services/posts/posts.js @@ -1,16 +1,15 @@ -import { db } from 'src/lib/db' +import { authDb, db } from 'src/lib/db' export const posts = (...args) => { - return db.post.findMany() + return authDb().post.findMany() } export const post = ({ id }) => { - return db.post.findUnique({ + return authDb().post.findUnique({ where: { id }, }) } export const Post = { - user: (_obj, { root }) => - db.post.findFirst({ where: { id: root.id } }).user(), + user: (_obj, { root }) => db.user.findUnique({ where: { id: root.userId } }), } diff --git a/web/src/components/Comment/Comment.js b/web/src/components/Comment/Comment.js index d6ae550e8..d737b1d13 100644 --- a/web/src/components/Comment/Comment.js +++ b/web/src/components/Comment/Comment.js @@ -43,15 +43,15 @@ const Comment = ({ comment }) => {

{comment.body}

- {hasRole('moderator') && ( - - )} + {/* {hasRole('moderator') && ( */} + + {/* )} */} ) } diff --git a/web/src/components/Post/Posts/Posts.js b/web/src/components/Post/Posts/Posts.js index 4c44edfaa..7ea34a27a 100644 --- a/web/src/components/Post/Posts/Posts.js +++ b/web/src/components/Post/Posts/Posts.js @@ -1,6 +1,6 @@ +import { Link, routes } from '@redwoodjs/router' import { useMutation } from '@redwoodjs/web' import { toast } from '@redwoodjs/web/toast' -import { Link, routes } from '@redwoodjs/router' import { QUERY } from 'src/components/Post/PostsCell' @@ -12,6 +12,22 @@ const DELETE_POST_MUTATION = gql` } ` +const PUBLISH_POST_MUTATION = gql` + mutation PublishPostMutation($id: Int!) { + publishPost(id: $id) { + id + } + } +` + +const UNPUBLISH_POST_MUTATION = gql` + mutation UnpublishPostMutation($id: Int!) { + unpublishPost(id: $id) { + id + } + } +` + const MAX_STRING_LENGTH = 150 const truncate = (text) => { @@ -50,6 +66,36 @@ const PostsList = ({ posts }) => { awaitRefetchQueries: true, }) + const [publishPost] = useMutation(PUBLISH_POST_MUTATION, { + onCompleted: () => { + toast.success('Post published') + }, + // This refetches the query on the list page. Read more about other ways to + // update the cache over here: + // https://www.apollographql.com/docs/react/data/mutations/#making-all-other-cache-updates + refetchQueries: [{ query: QUERY }], + awaitRefetchQueries: true, + }) + + const [unpublishPost] = useMutation(UNPUBLISH_POST_MUTATION, { + onCompleted: () => { + toast.success('Post unpublished') + }, + // This refetches the query on the list page. Read more about other ways to + // update the cache over here: + // https://www.apollographql.com/docs/react/data/mutations/#making-all-other-cache-updates + refetchQueries: [{ query: QUERY }], + awaitRefetchQueries: true, + }) + + const onPublishClick = (id) => { + publishPost({ variables: { id } }) + } + + const onUnpublishClick = (id) => { + unpublishPost({ variables: { id } }) + } + const onDeleteClick = (id) => { if (confirm('Are you sure you want to delete post ' + id + '?')) { deletePost({ variables: { id } }) @@ -91,6 +137,26 @@ const PostsList = ({ posts }) => { > Edit + {post.published && ( + onUnpublishClick(post.id)} + > + Unpublish + + )} + {!post.published && ( + onPublishClick(post.id)} + > + Publish + + )} ": + version: 1.22.2 + resolution: "resolve@patch:resolve@npm%3A1.22.2#~builtin::version=1.22.2&hash=07638b" + dependencies: + is-core-module: ^2.11.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: dcf068c4391941734efda06b6f778c013fd349cd4340f126de17c265a7b006c67de7e80e7aa06ecd29f3922e49f5561622b9faf98531f16aa9a896d22148c661 + languageName: node + linkType: hard + "resolve@patch:resolve@^2.0.0-next.3#~builtin": version: 2.0.0-next.3 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.3#~builtin::version=2.0.0-next.3&hash=07638b" @@ -20529,6 +21252,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.8": + version: 7.5.2 + resolution: "semver@npm:7.5.2" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: d151207ab762a8067f6302076edc04e5b8da2362eb9e3f21c2567ceadfd415064936d215b4aae7791da118c230649d29089be979ffa49c5b56a6bcf82147efdd + languageName: node + linkType: hard + "semver@npm:~7.0.0": version: 7.0.0 resolution: "semver@npm:7.0.0" @@ -20852,6 +21586,13 @@ __metadata: languageName: node linkType: hard +"sleep-promise@npm:^9.1.0": + version: 9.1.0 + resolution: "sleep-promise@npm:9.1.0" + checksum: 15813898a356d64818d34b437523fe64b0804694730d23201af171e7f3172843d8fcbdc1ba7eea0a0f5b76afa94277a64ef829f22abe3801e3eb2e3915092aa3 + languageName: node + linkType: hard + "slice-ansi@npm:^3.0.0": version: 3.0.0 resolution: "slice-ansi@npm:3.0.0" @@ -21118,6 +21859,13 @@ __metadata: languageName: node linkType: hard +"speedometer@npm:~1.0.0": + version: 1.0.0 + resolution: "speedometer@npm:1.0.0" + checksum: d5bfafd5721a1d5300be03f1f3dead419fcec715103dcfd72a1cb85d64a7fae1afaf7e5c80b974a44a4550eafa01ae034e0dbddb50be1dba3a58c9241ee8de54 + languageName: node + linkType: hard + "split-string@npm:^3.0.1, split-string@npm:^3.0.2": version: 3.1.0 resolution: "split-string@npm:3.1.0" @@ -21470,6 +22218,13 @@ __metadata: languageName: node linkType: hard +"strip-color@npm:^0.1.0": + version: 0.1.0 + resolution: "strip-color@npm:0.1.0" + checksum: 24df29b62eefc57a1d7f35c36f3b0c070289a0742aa54af2ff9ee39eadb19e372588a6faa2fa6a98870d97cb70c5d77826de69fff5e383fc2c2bdbf24cf1bcc8 + languageName: node + linkType: hard + "strip-eof@npm:^1.0.0": version: 1.0.0 resolution: "strip-eof@npm:1.0.0" @@ -21572,6 +22327,15 @@ __metadata: languageName: node linkType: hard +"superjson@npm:^1.11.0": + version: 1.12.3 + resolution: "superjson@npm:1.12.3" + dependencies: + copy-anything: ^3.0.2 + checksum: 2f5ad70a7bd807fef1c43b7238eec093199545f8914c90f14feb7091f2aca1ed6ddda35c524eefedb9afade4c1fcdd2b4e527a2b897e14c7f7f0d6988c1c9dce + languageName: node + linkType: hard + "supports-color@npm:^5.3.0, supports-color@npm:^5.5.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -21965,7 +22729,7 @@ __metadata: languageName: node linkType: hard -"through2@npm:^2.0.0, through2@npm:^2.0.1": +"through2@npm:^2.0.0, through2@npm:^2.0.1, through2@npm:~2.0.3": version: 2.0.5 resolution: "through2@npm:2.0.5" dependencies: @@ -22250,6 +23014,16 @@ __metadata: languageName: node linkType: hard +"ts-morph@npm:^16.0.0": + version: 16.0.0 + resolution: "ts-morph@npm:16.0.0" + dependencies: + "@ts-morph/common": ~0.17.0 + code-block-writer: ^11.0.3 + checksum: 8ab316db96ec9254daf1f7193e225393c4a48b814f2d0191cde9da5b380a5a8d8eb3f75fb82b841335d06dd6e9c477daf0b8986e5b5b5e172064065b0c2b7408 + languageName: node + linkType: hard + "ts-node@npm:^10.8.1": version: 10.9.1 resolution: "ts-node@npm:10.9.1" @@ -22288,6 +23062,13 @@ __metadata: languageName: node linkType: hard +"ts-pattern@npm:4.3.0": + version: 4.3.0 + resolution: "ts-pattern@npm:4.3.0" + checksum: e83d370feeda3657f5b9d49b119840e4c4f7cbee9797233078cbf1ad8f82dbd44935e8120ee3293d7626fed542b230827fdb87fcbdcaf37fd0f1d22345d05594 + languageName: node + linkType: hard + "ts-pattern@npm:^4.0.1": version: 4.0.2 resolution: "ts-pattern@npm:4.0.2" @@ -22345,6 +23126,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.4.1": + version: 2.5.3 + resolution: "tslib@npm:2.5.3" + checksum: 4cb1817d34fae5b27d146e6c4a468d4155097d95c1335d0bc9690f11f33e63844806bf4ed6d97c30c72b8d85261b66cbbe16d871d9c594ac05701ec83e62a607 + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -22363,6 +23151,15 @@ __metadata: languageName: node linkType: hard +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: ^5.0.1 + checksum: 4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a + languageName: node + linkType: hard + "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -22381,7 +23178,7 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8": +"type-detect@npm:4.0.8, type-detect@npm:^4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd @@ -22951,7 +23748,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:9.0.0": +"uuid@npm:9.0.0, uuid@npm:^9.0.0": version: 9.0.0 resolution: "uuid@npm:9.0.0" bin: @@ -23074,6 +23871,51 @@ __metadata: languageName: node linkType: hard +"vscode-jsonrpc@npm:8.0.2": + version: 8.0.2 + resolution: "vscode-jsonrpc@npm:8.0.2" + checksum: 349ae58c725d9d45f2f43914c0fdaf722a2f26669fb5f95f3200e1fd2520496936c20bee19144305dcbaa6ec2de438792444d3be365b110f454c377e37a70550 + languageName: node + linkType: hard + +"vscode-jsonrpc@npm:8.1.0, vscode-jsonrpc@npm:^8.0.2": + version: 8.1.0 + resolution: "vscode-jsonrpc@npm:8.1.0" + checksum: cb797b892227e7997378c6a1563b9849b6ec8190daae6fd18909fff4b33f6a98690bb315b95a3e4eeb9bf373d11896028c2111d71101544dfa16cb18a53cfcaa + languageName: node + linkType: hard + +"vscode-languageclient@npm:^8.0.2": + version: 8.1.0 + resolution: "vscode-languageclient@npm:8.1.0" + dependencies: + minimatch: ^5.1.0 + semver: ^7.3.7 + vscode-languageserver-protocol: 3.17.3 + checksum: dc141a91ec482289aa6e4510eb85bcca31aff8dab98fc369dd3edb45dc3e3b316ddc7618ea4f262a5c530cff21276b037e7b86cf408ee11ed8015b4268c5cba6 + languageName: node + linkType: hard + +"vscode-languageserver-protocol@npm:3.17.2": + version: 3.17.2 + resolution: "vscode-languageserver-protocol@npm:3.17.2" + dependencies: + vscode-jsonrpc: 8.0.2 + vscode-languageserver-types: 3.17.2 + checksum: 33b51bdbc69f0a3b680abc87d02372392afe139d092d47efc98dc716d1c4d71f6615f366698b563f88964554958e7097df5500c31ec739b21ea4e74b6d38254e + languageName: node + linkType: hard + +"vscode-languageserver-protocol@npm:3.17.3": + version: 3.17.3 + resolution: "vscode-languageserver-protocol@npm:3.17.3" + dependencies: + vscode-jsonrpc: 8.1.0 + vscode-languageserver-types: 3.17.3 + checksum: c8448cab8c8e3df386825f129a2f06a748510ad3407cb3d2122c6b661e973c32bb2f2612d64ea80e8c0344001adc80ee2f4c8bbce4fa81dd3eef888309457578 + languageName: node + linkType: hard + "vscode-languageserver-protocol@npm:^3.15.3": version: 3.16.0 resolution: "vscode-languageserver-protocol@npm:3.16.0" @@ -23091,6 +23933,13 @@ __metadata: languageName: node linkType: hard +"vscode-languageserver-textdocument@npm:^1.0.7, vscode-languageserver-textdocument@npm:~1.0.8": + version: 1.0.10 + resolution: "vscode-languageserver-textdocument@npm:1.0.10" + checksum: 0f4e82c262eaea2ed84e004c751453ac900a669763287dee92f9b631804a6ef1dabd62880185fa4d9a063f27d46e65c5251f7f0c8329aee5e3b3db6b6dfa2109 + languageName: node + linkType: hard + "vscode-languageserver-types@npm:3.16.0": version: 3.16.0 resolution: "vscode-languageserver-types@npm:3.16.0" @@ -23105,6 +23954,13 @@ __metadata: languageName: node linkType: hard +"vscode-languageserver-types@npm:3.17.3": + version: 3.17.3 + resolution: "vscode-languageserver-types@npm:3.17.3" + checksum: a70d4ac0dbc08ba425b97e329e6a5696dabc6a264415bacb861e10d859b224f1b46d1fb41c17b6fdd31b32749d3bdfc819cb1b8a57dbe0d1e70e661ba8cfa397 + languageName: node + linkType: hard + "vscode-languageserver@npm:6.1.1": version: 6.1.1 resolution: "vscode-languageserver@npm:6.1.1" @@ -23116,6 +23972,35 @@ __metadata: languageName: node linkType: hard +"vscode-languageserver@npm:^8.0.2": + version: 8.1.0 + resolution: "vscode-languageserver@npm:8.1.0" + dependencies: + vscode-languageserver-protocol: 3.17.3 + bin: + installServerIntoExtension: bin/installServerIntoExtension + checksum: 1c05a491bbb4fb5266b3cc7d0be3b267aea2ff0c8aece2d8fb35e0a1b86ccc9aee252d08becec97d292fd3e1cc099cf350d720fb6c1a19f64fd1ac360f6248ae + languageName: node + linkType: hard + +"vscode-languageserver@npm:~8.0.2": + version: 8.0.2 + resolution: "vscode-languageserver@npm:8.0.2" + dependencies: + vscode-languageserver-protocol: 3.17.2 + bin: + installServerIntoExtension: bin/installServerIntoExtension + checksum: 0dfe96f9c7ecee29ed3005cedde8a1055a6be2d56a28824328ea687d4df584227964888f55ec35d8d496db63869eda8c2782b3d2a97cf4685c9f7e24c20ef72e + languageName: node + linkType: hard + +"vscode-uri@npm:^3.0.6, vscode-uri@npm:~3.0.7": + version: 3.0.7 + resolution: "vscode-uri@npm:3.0.7" + checksum: 67bc15bc9c9bd2d70dae8b27f2a3164281c6ee8f6484e6c5945a44d89871da93d52f2ba339ebc12ab0c10991d4576171b5d85e49a542454329c16faf977e4c59 + languageName: node + linkType: hard + "w3c-hr-time@npm:^1.0.2": version: 1.0.2 resolution: "w3c-hr-time@npm:1.0.2" @@ -23665,6 +24550,13 @@ __metadata: languageName: node linkType: hard +"when@npm:": + version: 3.7.8 + resolution: "when@npm:3.7.8" + checksum: de42359a40d93d00890be87e6955928c247c026572e5b9ab141e1c62369bfe2832770c1dab00429d5823bc63850d1432de0aa0be963e95f0ca03c03f9a76022d + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.0.2": version: 1.0.2 resolution: "which-boxed-primitive@npm:1.0.2" @@ -24090,6 +24982,48 @@ __metadata: languageName: node linkType: hard +"zenstack@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "zenstack@npm:1.0.0-beta.2" + dependencies: + "@paralleldrive/cuid2": ^2.2.0 + "@prisma/generator-helper": ^4.0.0 + "@prisma/internals": ^4.0.0 + "@zenstackhq/language": 1.0.0-beta.2 + "@zenstackhq/sdk": 1.0.0-beta.2 + async-exit-hook: ^2.0.1 + change-case: ^4.1.2 + colors: 1.4.0 + commander: ^8.3.0 + get-latest-version: ^5.0.1 + langium: 1.2.0 + lower-case-first: ^2.0.2 + mixpanel: ^0.17.0 + node-machine-id: ^1.1.12 + ora: ^5.4.1 + pluralize: ^8.0.0 + promisify: ^0.0.3 + semver: ^7.3.8 + sleep-promise: ^9.1.0 + strip-color: ^0.1.0 + ts-morph: ^16.0.0 + upper-case-first: ^2.0.2 + uuid: ^9.0.0 + vscode-jsonrpc: ^8.0.2 + vscode-languageclient: ^8.0.2 + vscode-languageserver: ^8.0.2 + vscode-languageserver-textdocument: ^1.0.7 + vscode-uri: ^3.0.6 + zod: 3.21.1 + zod-validation-error: ^0.2.1 + peerDependencies: + prisma: ^4.0.0 + bin: + zenstack: bin/cli + checksum: d87c804b36f8130609e56a14ea0e8b2e31594dfb4c8e71683cf19850f6054d5f4e6b02222605525793099d16cc7e05fa29920239af400cb44b108e92b424650b + languageName: node + linkType: hard + "zip-stream@npm:^4.1.0": version: 4.1.0 resolution: "zip-stream@npm:4.1.0" @@ -24101,6 +25035,22 @@ __metadata: languageName: node linkType: hard +"zod-validation-error@npm:^0.2.1": + version: 0.2.2 + resolution: "zod-validation-error@npm:0.2.2" + peerDependencies: + zod: ^3.18.0 + checksum: 8c2cab8fc38e2b6b69719453cf6ecc12b92bf0ef32d9736da9811294522df2373703e98754686be33bd9a5df10c2d229a4422d0567680b94e063e2d924ddd12d + languageName: node + linkType: hard + +"zod@npm:3.21.1": + version: 3.21.1 + resolution: "zod@npm:3.21.1" + checksum: f089f1b53657fcbfea0888b5b75db2973c619006f8817707369a9332a4916ec6784c2cdb9e3cc03432c8dc815ed3f9bc05631e15829104a1bb1e6feb82f20a20 + languageName: node + linkType: hard + "zwitch@npm:^1.0.0": version: 1.0.5 resolution: "zwitch@npm:1.0.5"