diff --git a/.gitmodules b/.gitmodules index ae37663416..35cb73b2b6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1458,6 +1458,9 @@ [submodule "vendor/grammars/zeek-sublime"] path = vendor/grammars/zeek-sublime url = https://github.com/zeek/zeek-sublime +[submodule "vendor/grammars/zenstack"] + path = vendor/grammars/zenstack + url = https://github.com/zenstackhq/zenstack.git [submodule "vendor/grammars/zephir-sublime"] path = vendor/grammars/zephir-sublime url = https://github.com/phalcon/zephir-sublime diff --git a/grammars.yml b/grammars.yml index aaea7a1805..43ec32aa41 100644 --- a/grammars.yml +++ b/grammars.yml @@ -1319,5 +1319,7 @@ vendor/grammars/xml.tmbundle: - text.xml.xsl vendor/grammars/zeek-sublime: - source.zeek +vendor/grammars/zenstack: +- source.zmodel vendor/grammars/zephir-sublime: - source.php.zephir diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index bcba5cc180..913108fc2f 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -8806,3 +8806,11 @@ xBase: tm_scope: source.harbour ace_mode: text language_id: 421 +Zmodel: + type: data + color: "#ff7100" + extensions: + - ".zmodel" + tm_scope: source.zmodel + ace_mode: text + language_id: 803760908 \ No newline at end of file diff --git a/samples/Zmodel/blog.zmodel b/samples/Zmodel/blog.zmodel new file mode 100644 index 0000000000..6abc784d09 --- /dev/null +++ b/samples/Zmodel/blog.zmodel @@ -0,0 +1,88 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below + // Further reading: + // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema + // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string + url = env("DATABASE_URL") +} + +plugin hooks { + provider = '@zenstackhq/tanstack-query' + target = 'react' + version = 'v5' + output = "./src/lib/hooks" +} + +model Post { + id Int @id @default(autoincrement()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + createdBy User @relation(fields: [createdById], references: [id]) + createdById String + + @@index([name]) +} + +// Necessary for Next auth +model Account { + id String @id @default(cuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? // @db.Text + access_token String? // @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? // @db.Text + session_state String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + refresh_token_expires_in Int? + + @@unique([provider, providerAccountId]) +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model User { + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + password String @password @omit + image String? + accounts Account[] + sessions Session[] + posts Post[] + + // everyone can signup, and user profile is also publicly readable + @@allow('create,read', true) + + // only the user can update or delete their own profile + @@allow('update,delete', auth() == this) +} + +model VerificationToken { + identifier String + token String @unique + expires DateTime + + @@unique([identifier, token]) +} diff --git a/samples/Zmodel/saas.zmodel b/samples/Zmodel/saas.zmodel new file mode 100644 index 0000000000..955f7a80b4 --- /dev/null +++ b/samples/Zmodel/saas.zmodel @@ -0,0 +1,96 @@ +datasource db { + provider="sqlite" + url="file:./dev.db" +} + +generator client { + provider = "prisma-client-js" +} + +/** + * Model for a user + */ +model User { + id String @id @default(uuid()) + email String @unique + password String? @password @omit + name String? + orgs Organization[] + posts Post[] + groups Group[] + + // can be created by anyone, even not logged in + @@allow('create', true) + // can be read by users in the same organization + @@allow('read', orgs?[members?[auth() == this]]) + // full access by oneself + @@allow('all', auth() == this) +} + +/** + * Model for a organization + */ +model Organization { + id String @id @default(uuid()) + name String + members User[] + post Post[] + groups Group[] + + // everyone can create a organization + @@allow('create', true) + // any user in the organization can read the organization + @@allow('read', members?[auth().id == id]) +} + +/** +* Base model for all entites in a organization +*/ +abstract model organizationBaseEntity { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + isDeleted Boolean @default(false) @omit + isPublic Boolean @default(false) + owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) + ownerId String + org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade) + orgId String + groups Group[] + + // when create, owner must be set to current user, and user must be in the organization + @@allow('create', owner == auth() && org.members?[id == auth().id]) + // only the owner can update it and is not allowed to change the owner + @@allow('update', owner == auth() && org.members?[id == auth().id] && future().owner == owner) + // allow owner to read + @@allow('read', owner == auth()) + // allow shared group members to read it + @@allow('read', groups?[users?[id == auth().id]]) + // allow organization to access if public + @@allow('read', isPublic && org.members?[id == auth().id]) + // can not be read if deleted + @@deny('all', isDeleted == true) +} + +/** +* Model for a post +*/ +model Post extends organizationBaseEntity { + title String + content String +} + +/** + * Model for a group + */ +model Group { + id String @id @default(uuid()) + name String + users User[] + posts Post[] + org Organization @relation(fields: [orgId], references: [id]) + orgId String + + // group is shared by organization + @@allow('all', org.members?[auth().id == id]) +} \ No newline at end of file diff --git a/samples/Zmodel/todo.zmodel b/samples/Zmodel/todo.zmodel new file mode 100644 index 0000000000..25d918defd --- /dev/null +++ b/samples/Zmodel/todo.zmodel @@ -0,0 +1,178 @@ +/* +* Sample model for a collaborative Todo app +*/ + +datasource db { + provider = 'sqlite' + url = 'file:./dev.db' +} + +generator js { + provider = 'prisma-client-js' +} + +plugin enhancer { + provider = '@core/enhancer' + generatePermissionChecker = true +} + +plugin hooks { + provider = '@zenstackhq/swr' + output = 'lib/hooks' +} + + +/** + * Model for a space in which users can collaborate on Lists and Todos + */ +model Space { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + name String @length(4, 50) + slug String @unique @regex('^[0-9a-zA-Z]{4,16}$') + members SpaceUser[] + lists List[] + + // require login + @@deny('all', auth() == null) + + // everyone can create a space + @@allow('create', true) + + // any user in the space can read the space + @@allow('read', members?[user == auth()]) + + // space admin can update and delete + @@allow('update,delete', members?[user == auth() && role == 'ADMIN']) + + @@deny('update', future().members?[user == auth()]) +} + +/** + * Model representing membership of a user in a space + */ +model SpaceUser { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade) + spaceId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String + role String + @@unique([userId, spaceId]) + + // require login + @@deny('all', auth() == null) + + // space admin can create/update/delete + @@allow('create,update,delete', space.members?[user == auth() && role == 'ADMIN']) + + // user can read entries for spaces which he's a member of + @@allow('read', space.members?[user == auth()]) +} + +/** + * Model for a user + */ +model User { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + email String @unique @email + emailVerified DateTime? + password String? @password @omit + name String? + spaces SpaceUser[] + image String? @url + lists List[] + todos Todo[] + + // next-auth + accounts Account[] + + // can be created by anyone, even not logged in + @@allow('create', true) + + // can be read by users sharing any space + @@allow('read', spaces?[space.members?[user == auth()]]) + + // full access by oneself + @@allow('all', auth() == this) +} + +abstract model BaseEntity { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade) + spaceId String + owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) + ownerId String @default(auth().id) + + // can be read by owner or space members + @@allow('read', owner == auth() || (space.members?[user == auth()])) + + // when create, owner must be set to current user, and user must be in the space + @@allow('create', owner == auth() && space.members?[user == auth()]) + + // when create, owner must be set to current user, and user must be in the space + // update is not allowed to change owner + @@allow('update', owner == auth() && space.members?[user == auth()] && future().owner == owner) + + // can be deleted by owner + @@allow('delete', owner == auth()) +} + +/** + * Model for a Todo list + */ +model List extends BaseEntity { + title String @length(1, 100) + private Boolean @default(false) + todos Todo[] + + // can't be read by others if it's private + @@deny('read', private == true && owner != auth()) +} + +/** + * Model for a single Todo + */ +model Todo { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) + ownerId String @default(auth().id) + list List @relation(fields: [listId], references: [id], onDelete: Cascade) + listId String + title String @length(1, 100) + completedAt DateTime? + + // full access if the parent list is readable + @@allow('all', check(list, 'read')) +} + +/** + * Next-auth user account + */ +model Account { + id String @id @default(uuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? + refresh_token_expires_in Int? + access_token String? + expires_at Int? + token_type String? + scope String? + id_token String? + session_state String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + @@unique([provider, providerAccountId]) +} \ No newline at end of file diff --git a/vendor/README.md b/vendor/README.md index 9abe3a7046..cd00fb8876 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -670,6 +670,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **ZIL:** [tclem/vscode-zil-language](https://github.com/tclem/vscode-zil-language) - **Zeek:** [zeek/zeek-sublime](https://github.com/zeek/zeek-sublime) - **ZenScript:** [CraftTweaker/ZenScript-tmLanguage](https://github.com/CraftTweaker/ZenScript-tmLanguage) +- **Zmodel:** [zenstackhq/zenstack](https://github.com/zenstackhq/zenstack) - **Zephir:** [phalcon/zephir-sublime](https://github.com/phalcon/zephir-sublime) - **Zig:** [ziglang/sublime-zig-language](https://github.com/ziglang/sublime-zig-language) - **cURL Config:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) diff --git a/vendor/grammars/zenstack b/vendor/grammars/zenstack new file mode 160000 index 0000000000..be8c1c49c1 --- /dev/null +++ b/vendor/grammars/zenstack @@ -0,0 +1 @@ +Subproject commit be8c1c49c1c66fd2a7dab1d47c6e02af6401d8f5 diff --git a/vendor/licenses/git_submodule/zenstack.dep.yml b/vendor/licenses/git_submodule/zenstack.dep.yml new file mode 100644 index 0000000000..e068e29acf --- /dev/null +++ b/vendor/licenses/git_submodule/zenstack.dep.yml @@ -0,0 +1,32 @@ +name: zenstack +version: 689d013796342db88777bb75b478f0b9fce2598c +type: git_submodule +homepage: https://github.com/zenstackhq/zenstack.git +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) 2022 ZenStack + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +- sources: README.md + text: "[MIT](LICENSE)" +notices: []