Fullstack TypeScript toolkit that enhances Prisma ORM with flexible Authorization layer for RBAC/ABAC/PBAC/ReBAC, offering auto-generated type-safe APIs and frontend hooks.
Hi again, I have continued playing with the v2 alpha and may have run into an accidental stress test causing a crash due to heap memory.
I will post my zschema below. It is perhaps too big or too complex with all the relations / polymorphic relationships and may be an edge case, but I just wanted to bring this to your attention.
Prisma and zod schemas generate successfully but the PrismaClient enhancer does not. It runs for a few minutes and then crashes with the output shown. I have tried removing various combinations of models and I could get it to work sometimes, but with this schema, I was able to reproduce a crash each time.
Output:
> zenstack generate
⌛️ ZenStack CLI v2.0.0-alpha.1, running plugins
✔ Generating Prisma schema
✔ Generating Zod schemas
⠙ Generating PrismaClient enhancer
<--- Last few GCs --->
[33877:0x140008000] 298372 ms: Mark-Compact (reduce) 4082.3 (4107.0) -> 4082.3 (4104.0) MB, 41.79 / 0.00 ms (average mu = 0.128, current mu = 0.000) last resort; GC in old space requested
[33877:0x140008000] 298416 ms: Mark-Compact (reduce) 4082.3 (4104.0) -> 4082.3 (4104.0) MB, 43.46 / 0.00 ms (average mu = 0.067, current mu = 0.001) last resort; GC in old space requested
<--- JS stacktrace --->
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
----- Native stack trace -----
1: 0x10491553c node::Abort() [/Users/andrew/.nvm/versions/node/v20.11.0/bin/node]
2: 0x10491573c node::ModifyCodeGenerationFromStrings(v8::Local<v8::Context>, v8::Local<v8::Value>, bool) [/Users/andrew/.nvm/versions/node/v20.11.0/bin/node]
3: 0x104a9a6c4 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/Users/andrew/.nvm/versions/node/v20.11.0/bin/node]
4: 0x104c65194 v8::internal::MemoryController<v8::internal::V8HeapTrait>::MinimumAllocationLimitGrowingStep(v8::internal::Heap::HeapGrowingMode) [/Users/andrew/.nvm/versions/node/v20.11.0/bin/node]
5: 0x104c49240 v8::internal::Factory::AllocateRaw(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/Users/andrew/.nvm/versions/node/v20.11.0/bin/node]
datasource db {
provider = 'sqlite'
url = 'file:./dev.db'
}
generator client {
provider = "prisma-client-js"
}
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)
@@allow('all', auth().id == userId)
@@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)
@@allow('all', auth().id == userId)
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@allow('all', true)
@@unique([identifier, token])
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String
accounts Account[]
sessions Session[]
username String @unique @length(min: 4, max: 20)
about String? @length(max: 500)
location String? @length(max: 100)
role String @default("USER") @deny(operation: "update", auth().role != "ADMIN")
inserted_at DateTime @default(now())
updated_at DateTime @updatedAt() @default(now())
editComments EditComment[]
posts Post[]
rankings UserRanking[]
ratings UserRating[]
favorites UserFavorite[]
people Person[]
studios Studio[]
edits Edit[]
attachments Attachment[]
galleries Gallery[]
uploads UserUpload[]
maxUploadsPerDay Int @default(10)
maxEditsPerDay Int @default(10)
// 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)
}
abstract model UserEntityRelation {
entityId String?
entity Entity? @relation(fields: [entityId], references: [id], onUpdate: NoAction)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction)
// everyone can read
@@allow('read', true)
@@allow('create,update,delete', auth().id == this.userId)
@@unique([userId,entityId])
}
model UserUpload {
timestamp DateTime @default(now())
key String @id
url String @unique
size Int
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction)
@@allow('create', auth().id == userId)
@@allow('all', auth().role == "ADMIN")
}
model Post {
id Int @id @default(autoincrement())
title String @length(max: 100)
body String @length(max: 1000)
createdAt DateTime @default(now())
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: NoAction)
@@allow('read', true)
@@allow('create,update,delete', auth().id == authorId && auth().role == "ADMIN")
}
model Edit extends UserEntityRelation {
id String @id @default(cuid())
status String @default("PENDING") @allow('update', auth().role in ["ADMIN", "MODERATOR"])
type String @allow('update', false)
timestamp DateTime @default(now())
note String? @length(max: 300)
// for creates - createPayload & updates - data before diff is applied
data String?
// for updates
diff String?
comments EditComment[]
}
model EditComment {
id Int @id @default(autoincrement())
timestamp DateTime @default(now())
content String @length(max: 300)
editId String
edit Edit @relation(fields: [editId], references: [id], onUpdate: Cascade)
authorId String
author User @relation(fields: [authorId], references: [id], onUpdate: Cascade)
// everyone can read
@@allow('read', true)
@@allow('create,update,delete', auth().id == this.authorId || auth().role in ["ADMIN", "MODERATOR"])
}
model MetadataIdentifier {
id Int @default(autoincrement()) @id
identifier String
metadataSource String
MetadataSource MetadataSource @relation(fields: [metadataSource], references: [slug], onUpdate: Cascade)
entities Entity[]
@@unique([identifier, metadataSource])
@@allow('read', true)
@@allow('create,update,delete', auth().role in ["ADMIN", "MODERATOR"])
}
model MetadataSource {
slug String @id
name String @unique
identifierRegex String
desc String?
url String
icon String
identifiers MetadataIdentifier[]
@@allow('all', auth().role == "ADMIN")
}
model Attachment extends UserEntityRelation {
id String @id @default(cuid())
createdAt DateTime @default(now())
key String @unique
url String @unique
galleries Gallery[]
@@allow('delete', auth().role in ["ADMIN", "MODERATOR"])
}
model Entity {
id String @id @default(cuid())
name String
desc String?
attachments Attachment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt @default(now())
type String
status String @default("PENDING") // PENDING ON INITIAL CREATION
verified Boolean @default(false)
edits Edit[]
userRankings UserRanking[]
userFavorites UserFavorite[]
userRatings UserRating[]
metaIdentifiers MetadataIdentifier[]
@@delegate(type)
@@allow('read', true)
@@allow('create', auth() != null)
@@allow('update', auth().role in ["ADMIN", "MODERATOR"])
@@allow('delete', auth().role == "ADMIN")
}
model Person extends Entity {
studios Studio[]
owners User[]
clips Clip[]
events Event[]
galleries Gallery[]
}
model Studio extends Entity {
people Person[]
owners User[]
clips Clip[]
events Event[]
galleries Gallery[]
}
model Clip extends Entity {
url String?
people Person[]
studios Studio[]
galleries Gallery[]
}
model UserRanking extends UserEntityRelation {
id String @id @default(cuid())
rank Int @gte(1) @lte(100)
note String? @length(max: 300)
}
model UserFavorite extends UserEntityRelation {
id String @id @default(cuid())
favoritedAt DateTime @default(now())
}
model UserRating extends UserEntityRelation {
id String @id @default(cuid())
rating Int @gte(1) @lte(5)
note String? @length(max: 500)
ratedAt DateTime @default(now())
}
model Event {
id Int @id @default(autoincrement())
name String @length(max: 100)
desc String? @length(max: 500)
location String? @length(max: 100)
date DateTime?
people Person[]
studios Studio[]
@@allow('read', true)
@@allow('create,update,delete', auth().role == "ADMIN")
}
model Gallery {
id String @id @default(cuid())
studioId String?
personId String?
timestamp DateTime @default(now())
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: NoAction)
people Person[]
studios Studio[]
clips Clip[]
attachments Attachment[]
@@allow('read', true)
@@allow('create,update,delete', auth().id == this.authorId && auth().role == "ADMIN")
}
Environment (please complete the following information):
ZenStack version: 2.0.0-alpha.1
Prisma version: 5.10.2
Database type: sqlite (trying temporarily to use sqlite due to my previous issue preventing me from using postgres)
Description and expected behavior
Hi again, I have continued playing with the v2 alpha and may have run into an accidental stress test causing a crash due to heap memory.
I will post my zschema below. It is perhaps too big or too complex with all the relations / polymorphic relationships and may be an edge case, but I just wanted to bring this to your attention.
Prisma and zod schemas generate successfully but the PrismaClient enhancer does not. It runs for a few minutes and then crashes with the output shown. I have tried removing various combinations of models and I could get it to work sometimes, but with this schema, I was able to reproduce a crash each time.
Output:
Environment (please complete the following information):