diff --git a/package.json b/package.json index ea79546d4..8d08d09ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "2.14.2", + "version": "2.15.0", "description": "", "scripts": { "build": "pnpm -r --filter=\"!./packages/ide/*\" build", diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index 817d324e0..e5311c8b7 100644 --- a/packages/ide/jetbrains/build.gradle.kts +++ b/packages/ide/jetbrains/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.zenstack" -version = "2.14.2" +version = "2.15.0" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index 33706b548..c1f360c71 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "2.14.2", + "version": "2.15.0", "displayName": "ZenStack JetBrains IDE Plugin", "description": "ZenStack JetBrains IDE plugin", "homepage": "https://zenstack.dev", diff --git a/packages/language/package.json b/packages/language/package.json index 222d81bfa..ca7559088 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "2.14.2", + "version": "2.15.0", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/misc/redwood/package.json b/packages/misc/redwood/package.json index 6694ea62a..8eeb3b0da 100644 --- a/packages/misc/redwood/package.json +++ b/packages/misc/redwood/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/redwood", "displayName": "ZenStack RedwoodJS Integration", - "version": "2.14.2", + "version": "2.15.0", "description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.", "repository": { "type": "git", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index eeb9a4b4f..3d82af6be 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/openapi", "displayName": "ZenStack Plugin and Runtime for OpenAPI", - "version": "2.14.2", + "version": "2.15.0", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index b946993eb..2e2d68298 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/swr", "displayName": "ZenStack plugin for generating SWR hooks", - "version": "2.14.2", + "version": "2.15.0", "description": "ZenStack plugin for generating SWR hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index bec80688d..505cfee6d 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/tanstack-query", "displayName": "ZenStack plugin for generating tanstack-query hooks", - "version": "2.14.2", + "version": "2.15.0", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "exports": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index 3afd0c572..c03357046 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/trpc", "displayName": "ZenStack plugin for tRPC", - "version": "2.14.2", + "version": "2.15.0", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package-lock.json b/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package-lock.json index fee8d4f44..d2afbe91c 100644 --- a/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package-lock.json +++ b/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package-lock.json @@ -7,7 +7,7 @@ "name": "nuxt-app", "hasInstallScript": true, "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@trpc/client": "^10.45.2", "@trpc/server": "^10.45.2", "nuxt": "^3.14.1592", @@ -18,7 +18,7 @@ }, "devDependencies": { "esbuild": "^0.24.0", - "prisma": "6.7.x", + "prisma": "6.8.x", "typescript": "^5.6.2", "vue-tsc": "^2.1.10" } diff --git a/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package.json b/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package.json index 2e0a31ee6..27196d87f 100644 --- a/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package.json +++ b/packages/plugins/trpc/tests/projects/nuxt-trpc-v10/package.json @@ -10,7 +10,7 @@ "postinstall": "nuxt prepare" }, "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@trpc/client": "^10.45.2", "@trpc/server": "^10.45.2", "nuxt": "^3.14.1592", @@ -21,7 +21,7 @@ }, "devDependencies": { "esbuild": "^0.24.0", - "prisma": "6.7.x", + "prisma": "6.8.x", "typescript": "^5.6.2", "vue-tsc": "^2.1.10" } diff --git a/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package-lock.json b/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package-lock.json index f45b783fd..131cf5c05 100644 --- a/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package-lock.json +++ b/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package-lock.json @@ -7,7 +7,7 @@ "name": "nuxt-app", "hasInstallScript": true, "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@trpc/client": "^11.0.0-rc.563", "@trpc/server": "^11.0.0-rc.563", "nuxt": "^3.14.1592", @@ -18,7 +18,7 @@ }, "devDependencies": { "esbuild": "^0.24.0", - "prisma": "6.7.x", + "prisma": "6.8.x", "typescript": "^5.6.2", "vue-tsc": "^2.1.10" } diff --git a/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package.json b/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package.json index ebb9127d5..b420e972a 100644 --- a/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package.json +++ b/packages/plugins/trpc/tests/projects/nuxt-trpc-v11/package.json @@ -10,7 +10,7 @@ "postinstall": "nuxt prepare" }, "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@trpc/client": "^11.0.0-rc.563", "@trpc/server": "^11.0.0-rc.563", "nuxt": "^3.14.1592", @@ -21,7 +21,7 @@ }, "devDependencies": { "esbuild": "^0.24.0", - "prisma": "6.7.x", + "prisma": "6.8.x", "typescript": "^5.6.2", "vue-tsc": "^2.1.10" } diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v11/package-lock.json b/packages/plugins/trpc/tests/projects/t3-trpc-v11/package-lock.json index 2ca7fa783..715e6131b 100644 --- a/packages/plugins/trpc/tests/projects/t3-trpc-v11/package-lock.json +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v11/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.50.0", "@trpc/client": "^11.0.0-rc.446", @@ -33,7 +33,7 @@ "@typescript-eslint/parser": "^8.1.0", "eslint": "^8.57.0", "eslint-config-next": "^14.2.4", - "prisma": "6.7.x", + "prisma": "6.8.x", "typescript": "^5.5.3" } }, diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json b/packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json index 91b993cb3..ec275a98f 100644 --- a/packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v11/package.json @@ -15,7 +15,7 @@ "start": "next start" }, "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.50.0", "@trpc/client": "^11.0.0-rc.446", @@ -39,7 +39,7 @@ "@typescript-eslint/parser": "^8.1.0", "eslint": "^8.57.0", "eslint-config-next": "^14.2.4", - "prisma": "6.7.x", + "prisma": "6.8.x", "typescript": "^5.5.3" }, "ct3aMetadata": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index acb2e17e9..dc29f1f81 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "2.14.2", + "version": "2.15.0", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", @@ -116,7 +116,7 @@ "zod-validation-error": "^1.5.0" }, "peerDependencies": { - "@prisma/client": "5.0.0 - 6.7.x" + "@prisma/client": "5.0.0 - 6.8.x" }, "author": { "name": "ZenStack Team" diff --git a/packages/runtime/src/cross/model-meta.ts b/packages/runtime/src/cross/model-meta.ts index 727038fd4..ad8d2aa76 100644 --- a/packages/runtime/src/cross/model-meta.ts +++ b/packages/runtime/src/cross/model-meta.ts @@ -20,6 +20,11 @@ export type RuntimeAttribute = { */ export type FieldDefaultValueProvider = (userContext: unknown) => unknown; +/** + * Action to take when the related model is deleted or updated + */ +export type RelationAction = 'Cascade' | 'Restrict' | 'NoAction' | 'SetNull' | 'SetDefault'; + /** * Runtime information of a data model field */ @@ -74,6 +79,16 @@ export type FieldInfo = { */ isRelationOwner?: boolean; + /** + * Action to take when the related model is deleted. + */ + onDeleteAction?: RelationAction; + + /** + * Action to take when the related model is updated. + */ + onUpdateAction?: RelationAction; + /** * If the field is a foreign key field */ diff --git a/packages/runtime/src/enhancements/node/delegate.ts b/packages/runtime/src/enhancements/node/delegate.ts index 54c1abf96..1d7cd3f53 100644 --- a/packages/runtime/src/enhancements/node/delegate.ts +++ b/packages/runtime/src/enhancements/node/delegate.ts @@ -10,6 +10,7 @@ import { NestedWriteVisitor, clone, enumerate, + getFields, getIdFields, getModelInfo, isDelegateModel, @@ -816,12 +817,19 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { return super.updateMany(args); } - const simpleUpdateMany = Object.keys(args.data).every((key) => { + let simpleUpdateMany = Object.keys(args.data).every((key) => { // check if the `data` clause involves base fields const fieldInfo = resolveField(this.options.modelMeta, this.model, key); return !fieldInfo?.inheritedFrom; }); + // check if there are any `@updatedAt` fields from delegate base models + if (simpleUpdateMany) { + if (this.getUpdatedAtFromDelegateBases(this.model).length > 0) { + simpleUpdateMany = false; + } + } + return this.queryUtils.transaction(this.prisma, (tx) => this.doUpdateMany(tx, this.model, args, simpleUpdateMany) ); @@ -947,6 +955,13 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { return !fieldInfo?.inheritedFrom; }); + // check if there are any `@updatedAt` fields from delegate base models + if (simpleUpdateMany) { + if (this.getUpdatedAtFromDelegateBases(model).length > 0) { + simpleUpdateMany = false; + } + } + if (simpleUpdateMany) { // check if the `where` clause involves base fields simpleUpdateMany = Object.keys(args.where || {}).every((key) => { @@ -1053,6 +1068,15 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { delete data[field]; } } + + // if we're updating any field, we need to take care of updating `@updatedAt` + // fields inherited from delegate base models + if (Object.keys(data).length > 0) { + const updatedAtFields = this.getUpdatedAtFromDelegateBases(model); + for (const fieldInfo of updatedAtFields) { + this.injectBaseFieldData(model, fieldInfo, new Date(), data, 'update'); + } + } } // #endregion @@ -1136,19 +1160,90 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { } } - private async doDelete(db: CrudContract, model: string, args: any): Promise { + private async doDelete(db: CrudContract, model: string, args: any, readBack = true): Promise { this.injectWhereHierarchy(model, args.where); await this.injectSelectIncludeHierarchy(model, args); + // read relation entities that need to be cascade deleted before deleting the main entity + const cascadeDeletes = await this.getRelationDelegateEntitiesForCascadeDelete(db, model, args.where); + + let result: unknown = undefined; + if (cascadeDeletes.length > 0) { + // we'll need to do cascade deletes of relations, so first + // read the current entity before anything changes + if (readBack) { + result = await this.doFind(db, model, 'findUnique', args); + } + + // process cascade deletes of relations, this ensure their delegate base + // entities are deleted as well + await Promise.all( + cascadeDeletes.map(({ model, entity }) => this.doDelete(db, model, { where: entity }, false)) + ); + } + if (this.options.logPrismaQuery) { this.logger.info(`[delegate] \`delete\` ${this.getModelName(model)}: ${formatObject(args)}`); } - const result = await db[model].delete(args); - const idValues = this.queryUtils.getEntityIds(model, result); + + const deleteResult = await db[model].delete(args); + if (!result) { + result = this.assembleHierarchy(model, deleteResult); + } // recursively delete base entities (they all have the same id values) + const idValues = this.queryUtils.getEntityIds(model, deleteResult); await this.deleteBaseRecursively(db, model, idValues); - return this.assembleHierarchy(model, result); + + return result; + } + + private async getRelationDelegateEntitiesForCascadeDelete(db: CrudContract, model: string, where: any) { + if (!where || Object.keys(where).length === 0) { + throw new Error('where clause is required for cascade delete'); + } + + const cascadeDeletes: Array<{ model: string; entity: any }> = []; + const fields = getFields(this.options.modelMeta, model); + if (fields) { + for (const fieldInfo of Object.values(fields)) { + if (!fieldInfo.isDataModel) { + continue; + } + + if (fieldInfo.isRelationOwner) { + // this side of the relation owns the foreign key, + // so it won't cause cascade delete to the other side + continue; + } + + if (fieldInfo.backLink) { + // get the opposite side of the relation + const backLinkField = this.queryUtils.getModelField(fieldInfo.type, fieldInfo.backLink); + + if (backLinkField?.isRelationOwner && this.isFieldCascadeDelete(backLinkField)) { + // if the opposite side of the relation is to be cascade deleted, + // recursively delete the delegate base entities + const relationModel = getModelInfo(this.options.modelMeta, fieldInfo.type); + if (relationModel?.baseTypes && relationModel.baseTypes.length > 0) { + // the relation model has delegate base, cascade the delete to the base + const relationEntities = await db[relationModel.name].findMany({ + where: { [backLinkField.name]: where }, + select: this.queryUtils.makeIdSelection(relationModel.name), + }); + relationEntities.forEach((entity) => { + cascadeDeletes.push({ model: fieldInfo.type, entity }); + }); + } + } + } + } + } + return cascadeDeletes; + } + + private isFieldCascadeDelete(fieldInfo: FieldInfo) { + return fieldInfo.onDeleteAction === 'Cascade'; } // #endregion @@ -1477,5 +1572,23 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { return result; } + private getUpdatedAtFromDelegateBases(model: string) { + const result: FieldInfo[] = []; + const modelFields = getFields(this.options.modelMeta, model); + if (!modelFields) { + return result; + } + for (const fieldInfo of Object.values(modelFields)) { + if ( + fieldInfo.attributes?.some((attr) => attr.name === '@updatedAt') && + fieldInfo.inheritedFrom && + isDelegateModel(this.options.modelMeta, fieldInfo.inheritedFrom) + ) { + result.push(fieldInfo); + } + } + return result; + } + // #endregion } diff --git a/packages/runtime/src/enhancements/node/policy/policy-utils.ts b/packages/runtime/src/enhancements/node/policy/policy-utils.ts index 0e655a3e5..2ee389476 100644 --- a/packages/runtime/src/enhancements/node/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/node/policy/policy-utils.ts @@ -1204,47 +1204,42 @@ export class PolicyUtil extends QueryUtils { return; } - let target: any; // injection target - let isInclude = false; // if the target is include or select - - if (args.select) { - target = args.select; - isInclude = false; - } else if (args.include) { - target = args.include; - isInclude = true; - } else { - target = args.select = this.makeAllScalarFieldSelect(model); - isInclude = false; - } - - if (!isInclude) { - // merge selects - for (const [k, v] of Object.entries(input.select)) { - if (v === true) { - if (!target[k]) { - target[k] = true; - } + // process scalar field selections first + for (const [k, v] of Object.entries(input.select)) { + const field = resolveField(this.modelMeta, model, k); + if (!field || field.isDataModel) { + continue; + } + if (v === true) { + if (!args.select) { + // do nothing since all scalar fields are selected by default + } else if (args.include) { + // do nothing since include implies selecting all scalar fields + } else { + args.select[k] = true; } } } - // recurse into nested selects (relation fields) + // process relation selections for (const [k, v] of Object.entries(input.select)) { - if (typeof v === 'object' && v?.select) { - const field = resolveField(this.modelMeta, model, k); - if (field?.isDataModel) { - // recurse into relation - if (isInclude && target[k] === true) { - // select all fields for the relation - target[k] = { select: this.makeAllScalarFieldSelect(field.type) }; - } else if (!target[k]) { - // ensure an empty select clause - target[k] = { select: {} }; - } - // recurse - this.doInjectReadCheckSelect(field.type, target[k], v); - } + const field = resolveField(this.modelMeta, model, k); + if (!field || !field.isDataModel) { + continue; + } + + // prepare the next level of args + let nextArgs = args.select ?? args.include; + if (!nextArgs) { + nextArgs = args.include = {}; + } + if (!nextArgs[k] || typeof nextArgs[k] !== 'object') { + nextArgs[k] = {}; + } + + if (v && typeof v === 'object') { + // recurse into relation + this.doInjectReadCheckSelect(field.type, nextArgs[k], v); } } } diff --git a/packages/runtime/src/enhancements/node/utils.ts b/packages/runtime/src/enhancements/node/utils.ts index 347447618..05d906046 100644 --- a/packages/runtime/src/enhancements/node/utils.ts +++ b/packages/runtime/src/enhancements/node/utils.ts @@ -7,7 +7,7 @@ import type { DbClientContract } from '../../types'; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function formatObject(value: any, multiLine = true) { - return multiLine ? safeJsonStringify(value, undefined, 2) : safeJsonStringify(value); + return safeJsonStringify(value, (_, v) => (typeof v === 'bigint' ? v.toString() : v), multiLine ? 2 : undefined); } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/schema/package.json b/packages/schema/package.json index 0be561296..4e6b08ff4 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "FullStack enhancement for Prisma ORM: seamless integration from database to UI", - "version": "2.14.2", + "version": "2.15.0", "author": { "name": "ZenStack Team" }, @@ -123,10 +123,10 @@ "zod-validation-error": "^1.5.0" }, "peerDependencies": { - "prisma": "5.0.0 - 6.7.x" + "prisma": "5.0.0 - 6.8.x" }, "devDependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@types/async-exit-hook": "^2.0.0", "@types/pluralize": "^0.0.29", "@types/semver": "^7.3.13", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 0bd0fccdc..c0801b805 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "2.14.2", + "version": "2.15.0", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { @@ -18,8 +18,8 @@ "author": "", "license": "MIT", "dependencies": { - "@prisma/generator-helper": "6.7.x", - "@prisma/internals": "6.7.x", + "@prisma/generator-helper": "6.8.x", + "@prisma/internals": "6.8.x", "@zenstackhq/language": "workspace:*", "@zenstackhq/runtime": "workspace:*", "langium": "1.3.1", diff --git a/packages/sdk/src/model-meta-generator.ts b/packages/sdk/src/model-meta-generator.ts index c5b866417..716e6ad7d 100644 --- a/packages/sdk/src/model-meta-generator.ts +++ b/packages/sdk/src/model-meta-generator.ts @@ -311,6 +311,18 @@ function writeFields( isRelationOwner: true,`); } + const onDeleteAction = getOnDeleteAction(dmField); + if (onDeleteAction) { + writer.write(` + onDeleteAction: '${onDeleteAction}',`); + } + + const onUpdateAction = getOnUpdateAction(dmField); + if (onUpdateAction) { + writer.write(` + onUpdateAction: '${onUpdateAction}',`); + } + if (isForeignKeyField(dmField)) { writer.write(` isForeignKey: true,`); @@ -568,3 +580,25 @@ function writeShortNameMap(options: ModelMetaGeneratorOptions, writer: CodeWrite writer.write(','); } } + +function getOnDeleteAction(fieldInfo: DataModelField) { + const relationAttr = getAttribute(fieldInfo, '@relation'); + if (relationAttr) { + const onDelete = getAttributeArg(relationAttr, 'onDelete'); + if (onDelete && isEnumFieldReference(onDelete)) { + return onDelete.target.ref?.name; + } + } + return undefined; +} + +function getOnUpdateAction(fieldInfo: DataModelField) { + const relationAttr = getAttribute(fieldInfo, '@relation'); + if (relationAttr) { + const onUpdate = getAttributeArg(relationAttr, 'onUpdate'); + if (onUpdate && isEnumFieldReference(onUpdate)) { + return onUpdate.target.ref?.name; + } + } + return undefined; +} diff --git a/packages/server/package.json b/packages/server/package.json index dde13ffb7..c3a5288a3 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "2.14.2", + "version": "2.15.0", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", @@ -21,7 +21,8 @@ "express", "nextjs", "sveltekit", - "nuxtjs" + "nuxtjs", + "elysia" ], "author": "ZenStack Team", "license": "MIT", @@ -48,6 +49,7 @@ "@types/supertest": "^2.0.12", "@zenstackhq/testtools": "workspace:*", "body-parser": "^1.20.2", + "elysia": "^1.3.1", "express": "^4.19.2", "fastify": "^4.14.1", "fastify-plugin": "^4.5.0", diff --git a/packages/server/src/elysia/handler.ts b/packages/server/src/elysia/handler.ts new file mode 100644 index 000000000..f3bb72b1d --- /dev/null +++ b/packages/server/src/elysia/handler.ts @@ -0,0 +1,82 @@ +import { DbClientContract } from '@zenstackhq/runtime'; +import { Elysia, Context as ElysiaContext } from 'elysia'; +import { RPCApiHandler } from '../api'; +import { loadAssets } from '../shared'; +import { AdapterBaseOptions } from '../types'; + +/** + * Options for initializing an Elysia middleware. + */ +export interface ElysiaOptions extends AdapterBaseOptions { + /** + * Callback method for getting a Prisma instance for the given request context. + */ + getPrisma: (context: ElysiaContext) => Promise | unknown; + /** + * Optional base path to strip from the request path before passing to the API handler. + */ + basePath?: string; +} + +/** + * Creates an Elysia middleware handler for ZenStack. + * This handler provides automatic CRUD APIs through Elysia's routing system. + */ +export function createElysiaHandler(options: ElysiaOptions) { + const { modelMeta, zodSchemas } = loadAssets(options); + const requestHandler = options.handler ?? RPCApiHandler(); + + return async (app: Elysia) => { + app.all('/*', async (ctx: ElysiaContext) => { + const { request, body, set } = ctx; + const prisma = (await options.getPrisma(ctx)) as DbClientContract; + if (!prisma) { + set.status = 500; + return { + message: 'unable to get prisma from request context', + }; + } + + const url = new URL(request.url); + const query = Object.fromEntries(url.searchParams); + let path = url.pathname; + + if (options.basePath && path.startsWith(options.basePath)) { + path = path.slice(options.basePath.length); + if (!path.startsWith('/')) { + path = '/' + path; + } + } + + if (!path || path === '/') { + set.status = 400; + return { + message: 'missing path parameter', + }; + } + + try { + const r = await requestHandler({ + method: request.method, + path, + query, + requestBody: body, + prisma, + modelMeta, + zodSchemas, + logger: options.logger, + }); + + set.status = r.status; + return r.body; + } catch (err) { + set.status = 500; + return { + message: 'An internal server error occurred', + }; + } + }); + + return app; + }; +} diff --git a/packages/server/src/elysia/index.ts b/packages/server/src/elysia/index.ts new file mode 100644 index 000000000..bc0d7e165 --- /dev/null +++ b/packages/server/src/elysia/index.ts @@ -0,0 +1 @@ +export * from './handler'; \ No newline at end of file diff --git a/packages/server/tests/adapter/elysia.test.ts b/packages/server/tests/adapter/elysia.test.ts new file mode 100644 index 000000000..04be05715 --- /dev/null +++ b/packages/server/tests/adapter/elysia.test.ts @@ -0,0 +1,200 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/// +import { loadSchema } from '@zenstackhq/testtools'; +import 'isomorphic-fetch'; +import path from 'path'; +import superjson from 'superjson'; +import Rest from '../../src/api/rest'; +import { createElysiaHandler } from '../../src/elysia'; +import { makeUrl, schema } from '../utils'; +import { Elysia } from 'elysia'; + +describe('Elysia adapter tests - rpc handler', () => { + it('run hooks regular json', async () => { + const { prisma, zodSchemas } = await loadSchema(schema); + + const handler = await createElysiaApp( + createElysiaHandler({ getPrisma: () => prisma, zodSchemas, basePath: '/api' }) + ); + + let r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(0); + + r = await handler( + makeRequest('POST', '/api/user/create', { + include: { posts: true }, + data: { + id: 'user1', + email: 'user1@abc.com', + posts: { + create: [ + { title: 'post1', published: true, viewCount: 1 }, + { title: 'post2', published: false, viewCount: 2 }, + ], + }, + }, + }) + ); + expect(r.status).toBe(201); + expect((await unmarshal(r)).data).toMatchObject({ + email: 'user1@abc.com', + posts: expect.arrayContaining([ + expect.objectContaining({ title: 'post1' }), + expect.objectContaining({ title: 'post2' }), + ]), + }); + + r = await handler(makeRequest('GET', makeUrl('/api/post/findMany'))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(2); + + r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(1); + + r = await handler( + makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: 'user1@def.com' } }) + ); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data.email).toBe('user1@def.com'); + + r = await handler(makeRequest('GET', makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toBe(1); + + r = await handler(makeRequest('GET', makeUrl('/api/post/aggregate', { _sum: { viewCount: true } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data._sum.viewCount).toBe(3); + + r = await handler( + makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } })) + ); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toEqual( + expect.arrayContaining([ + expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), + expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), + ]) + ); + + r = await handler(makeRequest('DELETE', makeUrl('/api/user/deleteMany', { where: { id: 'user1' } }))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data.count).toBe(1); + }); + + it('custom load path', async () => { + const { prisma, projectDir } = await loadSchema(schema, { output: './zen' }); + + const handler = await createElysiaApp( + createElysiaHandler({ + getPrisma: () => prisma, + basePath: '/api', + modelMeta: require(path.join(projectDir, './zen/model-meta')).default, + zodSchemas: require(path.join(projectDir, './zen/zod')), + }) + ); + + const r = await handler( + makeRequest('POST', '/api/user/create', { + include: { posts: true }, + data: { + id: 'user1', + email: 'user1@abc.com', + posts: { + create: [ + { title: 'post1', published: true, viewCount: 1 }, + { title: 'post2', published: false, viewCount: 2 }, + ], + }, + }, + }) + ); + expect(r.status).toBe(201); + }); +}); + +describe('Elysia adapter tests - rest handler', () => { + it('run hooks', async () => { + const { prisma, modelMeta, zodSchemas } = await loadSchema(schema); + + const handler = await createElysiaApp( + createElysiaHandler({ + getPrisma: () => prisma, + basePath: '/api', + handler: Rest({ endpoint: 'http://localhost/api' }), + modelMeta, + zodSchemas, + }) + ); + + let r = await handler(makeRequest('GET', makeUrl('/api/post/1'))); + expect(r.status).toBe(404); + + r = await handler( + makeRequest('POST', '/api/user', { + data: { + type: 'user', + attributes: { id: 'user1', email: 'user1@abc.com' }, + }, + }) + ); + expect(r.status).toBe(201); + expect(await unmarshal(r)).toMatchObject({ + data: { + id: 'user1', + attributes: { + email: 'user1@abc.com', + }, + }, + }); + + r = await handler(makeRequest('GET', makeUrl('/api/user?filter[id]=user1'))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(1); + + r = await handler(makeRequest('GET', makeUrl('/api/user?filter[id]=user2'))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(0); + + r = await handler(makeRequest('GET', makeUrl('/api/user?filter[id]=user1&filter[email]=xyz'))); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data).toHaveLength(0); + + r = await handler( + makeRequest('PUT', makeUrl('/api/user/user1'), { + data: { type: 'user', attributes: { email: 'user1@def.com' } }, + }) + ); + expect(r.status).toBe(200); + expect((await unmarshal(r)).data.attributes.email).toBe('user1@def.com'); + + r = await handler(makeRequest('DELETE', makeUrl('/api/user/user1'))); + expect(r.status).toBe(200); + expect(await prisma.user.findMany()).toHaveLength(0); + }); +}); + +function makeRequest(method: string, path: string, body?: any) { + if (body) { + return new Request(`http://localhost${path}`, { + method, + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json' }, + }); + } else { + return new Request(`http://localhost${path}`, { method }); + } +} + +async function unmarshal(r: Response, useSuperJson = false) { + const text = await r.text(); + return (useSuperJson ? superjson.parse(text) : JSON.parse(text)) as any; +} + +async function createElysiaApp(middleware: (app: Elysia) => Promise) { + const app = new Elysia(); + await middleware(app); + return app.handle; +} diff --git a/packages/server/tests/adapter/hono.test.ts b/packages/server/tests/adapter/hono.test.ts index fc55e1647..28c1447fc 100644 --- a/packages/server/tests/adapter/hono.test.ts +++ b/packages/server/tests/adapter/hono.test.ts @@ -166,7 +166,7 @@ describe('Hono adapter tests - rest handler', () => { expect(r.status).toBe(200); expect((await unmarshal(r)).data.attributes.email).toBe('user1@def.com'); - r = await handler(makeRequest('DELETE', makeUrl(makeUrl('/api/user/user1')))); + r = await handler(makeRequest('DELETE', makeUrl('/api/user/user1'))); expect(r.status).toBe(200); expect(await prisma.user.findMany()).toHaveLength(0); }); diff --git a/packages/server/tests/adapter/sveltekit.test.ts b/packages/server/tests/adapter/sveltekit.test.ts index 650f89f85..9a447059f 100644 --- a/packages/server/tests/adapter/sveltekit.test.ts +++ b/packages/server/tests/adapter/sveltekit.test.ts @@ -163,7 +163,7 @@ describe('SvelteKit adapter tests - rest handler', () => { expect(r.status).toBe(200); expect((await unmarshal(r)).data.attributes.email).toBe('user1@def.com'); - r = await handler(makeRequest('DELETE', makeUrl(makeUrl('/api/user/user1')))); + r = await handler(makeRequest('DELETE', makeUrl('/api/user/user1'))); expect(r.status).toBe(200); expect(await prisma.user.findMany()).toHaveLength(0); }); diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 118b87019..6541dc1ea 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "2.14.2", + "version": "2.15.0", "description": "ZenStack Test Tools", "main": "index.js", "private": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e98dbcd2..acd0182f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -392,8 +392,8 @@ importers: packages/runtime: dependencies: '@prisma/client': - specifier: 5.0.0 - 6.7.x - version: 6.7.0(prisma@6.0.0)(typescript@5.5.2) + specifier: 5.0.0 - 6.8.x + version: 6.8.2(prisma@6.0.0)(typescript@5.5.2) bcryptjs: specifier: ^2.4.3 version: 2.4.3 @@ -523,7 +523,7 @@ importers: specifier: ^4.0.0 version: 4.0.1 prisma: - specifier: 5.0.0 - 6.7.x + specifier: 5.0.0 - 6.8.x version: 6.0.0 semver: specifier: ^7.5.2 @@ -575,8 +575,8 @@ importers: version: 1.5.0(zod@3.23.8) devDependencies: '@prisma/client': - specifier: 6.7.x - version: 6.7.0(prisma@6.0.0)(typescript@5.5.2) + specifier: 6.8.x + version: 6.8.2(prisma@6.0.0)(typescript@5.5.2) '@types/async-exit-hook': specifier: ^2.0.0 version: 2.0.2 @@ -627,11 +627,11 @@ importers: packages/sdk: dependencies: '@prisma/generator-helper': - specifier: 6.7.x - version: 6.7.0 + specifier: 6.8.x + version: 6.8.2 '@prisma/internals': - specifier: 6.7.x - version: 6.7.0(typescript@5.5.2) + specifier: 6.8.x + version: 6.8.2(typescript@5.5.2) '@zenstackhq/language': specifier: workspace:* version: link:../language/dist @@ -725,6 +725,9 @@ importers: body-parser: specifier: ^1.20.2 version: 1.20.2 + elysia: + specifier: ^1.3.1 + version: 1.3.1(exact-mirror@0.1.2(@sinclair/typebox@0.34.33))(file-type@20.5.0)(typescript@5.5.2) express: specifier: ^4.19.2 version: 4.19.2 @@ -2574,8 +2577,8 @@ packages: prisma: optional: true - '@prisma/client@6.7.0': - resolution: {integrity: sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==} + '@prisma/client@6.8.2': + resolution: {integrity: sha512-5II+vbyzv4si6Yunwgkj0qT/iY0zyspttoDrL3R4BYgLdp42/d2C8xdi9vqkrYtKt9H32oFIukvyw3Koz5JoDg==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -2586,8 +2589,8 @@ packages: typescript: optional: true - '@prisma/config@6.7.0': - resolution: {integrity: sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==} + '@prisma/config@6.8.2': + resolution: {integrity: sha512-ZJY1fF4qRBPdLQ/60wxNtX+eu89c3AkYEcP7L3jkp0IPXCNphCYxikTg55kPJLDOG6P0X+QG5tCv6CmsBRZWFQ==} '@prisma/debug@5.14.0': resolution: {integrity: sha512-iq56qBZuFfX3fCxoxT8gBX33lQzomBU0qIUaEj1RebsKVz1ob/BVH1XSBwwwvRVtZEV1b7Fxx2eVu34Ge/mg3w==} @@ -2595,14 +2598,14 @@ packages: '@prisma/debug@6.0.0': resolution: {integrity: sha512-eUjoNThlDXdyJ1iQ2d7U6aTVwm59EwvODb5zFVNJEokNoSiQmiYWNzZIwZyDmZ+j51j42/0iTaHIJ4/aZPKFRg==} - '@prisma/debug@6.7.0': - resolution: {integrity: sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==} + '@prisma/debug@6.8.2': + resolution: {integrity: sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==} - '@prisma/dmmf@6.7.0': - resolution: {integrity: sha512-Nabzf5H7KUwYV/1u8R5gTQ7x3ffdPIhtxVJsjhnLOjeuem8YzVu39jOgQbiOlfOvjU4sPYkV9wEXc5tIHumdUw==} + '@prisma/dmmf@6.8.2': + resolution: {integrity: sha512-okGJF/7hQZam/2wt+Y0hPyNxyY5S+L0FzAgtL932Q3YxUWHusRllrN39bCV45oF3QWY992g7rTWYdL2Rynt1qg==} - '@prisma/driver-adapter-utils@6.7.0': - resolution: {integrity: sha512-xYcXALWz1GsCRqwcDw0kP0R27chn99Co/HMX0nyOvIjAOo+41Tl/qcCOce/Ik1wNMGTI68N64kt3iccJ4EJoCQ==} + '@prisma/driver-adapter-utils@6.8.2': + resolution: {integrity: sha512-5+CzN/41gBsRmA3ekbVy1TXnSImSPBtMlxWAttVH6tg94bv4zGGRmyk5tUCdT83nl0hG1Sq2oMXR7ml6aqILvw==} '@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': resolution: {integrity: sha512-ip6pNkRo1UxWv+6toxNcYvItNYaqQjXdFNGJ+Nuk2eYtRoEdoF13wxo7/jsClJFFenMPVNVqXQDV0oveXnR1cA==} @@ -2610,8 +2613,8 @@ packages: '@prisma/engines-version@5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e': resolution: {integrity: sha512-JmIds0Q2/vsOmnuTJYxY4LE+sajqjYKhLtdOT6y4imojqv5d/aeVEfbBGC74t8Be1uSp0OP8lxIj2OqoKbLsfQ==} - '@prisma/engines-version@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed': - resolution: {integrity: sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==} + '@prisma/engines-version@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e': + resolution: {integrity: sha512-Rkik9lMyHpFNGaLpPF3H5q5TQTkm/aE7DsGM5m92FZTvWQsvmi6Va8On3pWvqLHOt5aPUvFb/FeZTmphI4CPiQ==} '@prisma/engines@5.14.0': resolution: {integrity: sha512-lgxkKZ6IEygVcw6IZZUlPIfLQ9hjSYAtHjZ5r64sCLDgVzsPFCi2XBBJgzPMkOQ5RHzUD4E/dVdpn9+ez8tk1A==} @@ -2619,8 +2622,8 @@ packages: '@prisma/engines@6.0.0': resolution: {integrity: sha512-ZZCVP3q22ifN6Ex6C8RIcTDBlRtMJS2H1ljV0knCiWNGArvvkEbE88W3uDdq/l4+UvyvHpGzdf9ZsCWSQR7ZQQ==} - '@prisma/engines@6.7.0': - resolution: {integrity: sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==} + '@prisma/engines@6.8.2': + resolution: {integrity: sha512-XqAJ//LXjqYRQ1RRabs79KOY4+v6gZOGzbcwDQl0D6n9WBKjV7qdrbd042CwSK0v0lM9MSHsbcFnU2Yn7z8Zlw==} '@prisma/fetch-engine@5.14.0': resolution: {integrity: sha512-VrheA9y9DMURK5vu8OJoOgQpxOhas3qF0IBHJ8G/0X44k82kc8E0w98HCn2nhnbOOMwbWsJWXfLC2/F8n5u0gQ==} @@ -2628,17 +2631,17 @@ packages: '@prisma/fetch-engine@6.0.0': resolution: {integrity: sha512-j2m+iO5RDPRI7SUc7sHo8wX7SA4iTkJ+18Sxch8KinQM46YiCQD1iXKN6qU79C1Fliw5Bw/qDyTHaTsa3JMerA==} - '@prisma/fetch-engine@6.7.0': - resolution: {integrity: sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==} + '@prisma/fetch-engine@6.8.2': + resolution: {integrity: sha512-lCvikWOgaLOfqXGacEKSNeenvj0n3qR5QvZUOmPE2e1Eh8cMYSobxonCg9rqM6FSdTfbpqp9xwhSAOYfNqSW0g==} '@prisma/generator-helper@5.14.0': resolution: {integrity: sha512-xVc71cmTnPZ0lnSs4FAY6Ta72vFJ3webrQwKMQ2ujr6hDG1VPIEf820T1TOS3ZZQd/OKigNKXnq3co8biz9/qw==} - '@prisma/generator-helper@6.7.0': - resolution: {integrity: sha512-z4ey7n8eUFx7Ol7RJCoEo9SVK2roy80qLXdrUFlmswcs0e/z03wDl4tpaA02BE2Yi9KCZl2Tkd6oMes81vUefA==} + '@prisma/generator-helper@6.8.2': + resolution: {integrity: sha512-KBLW47sbwFBKKYMiICIAEWsG6TdpPapPT7e7hpmpF3xMgYAm6YIXu4JGwfQVDY9Vbcb+0vPdPdSEQtInYOOe5g==} - '@prisma/generator@6.7.0': - resolution: {integrity: sha512-wCsD7QJn1JBKJfv5uOMhwBCvZPGerPJ3EC2HocFPFaHSzVIXLi/zNb/8gpBxervOXTbQRJ2dpqvMsJnwAnPi8A==} + '@prisma/generator@6.8.2': + resolution: {integrity: sha512-yExkvgqiKg1WHzjYttz40g5DsOtud8RhapM0Mum6pw+wrDoQyhSAxs5NHyFCV+9VPvRd2v+jAP2CTT07bsibjw==} '@prisma/get-platform@5.14.0': resolution: {integrity: sha512-/yAyBvcEjRv41ynZrhdrPtHgk47xLRRq/o5eWGcUpBJ1YrUZTYB8EoPiopnP7iQrMATK8stXQdPOoVlrzuTQZw==} @@ -2646,14 +2649,14 @@ packages: '@prisma/get-platform@6.0.0': resolution: {integrity: sha512-PS6nYyIm9g8C03E4y7LknOfdCw/t2KyEJxntMPQHQZCOUgOpF82Ma60mdlOD08w90I3fjLiZZ0+MadenR3naDQ==} - '@prisma/get-platform@6.7.0': - resolution: {integrity: sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==} + '@prisma/get-platform@6.8.2': + resolution: {integrity: sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==} '@prisma/internals@5.14.0': resolution: {integrity: sha512-s0JRNDmR2bvcyy0toz89jy7SbbjANAs4e9KCReNvSm5czctIaZzDf68tcOXdtH0G7m9mKhVhNPdS9lMky0DhWA==} - '@prisma/internals@6.7.0': - resolution: {integrity: sha512-TeIw9cPf5y0ULw5yOUKxVK/p6J/lvCMIAFzQQ3aSjlg5+XaNxyK8S77J1C2T/kI50/xIPJXiytjv3baUKKVzlA==} + '@prisma/internals@6.8.2': + resolution: {integrity: sha512-lDZrOCUW+ubBTXfEgun969eHVc+Uiq2W7YfF/bWZ0hGX2wLGQsgLGXsRw6AxyvWNXR85BbSPQ2qVHzKtWP9qXA==} peerDependencies: typescript: '>=5.1.0' peerDependenciesMeta: @@ -2666,17 +2669,17 @@ packages: '@prisma/prisma-schema-wasm@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': resolution: {integrity: sha512-WeTmJ0mK8ALoKJUQFO+465k9lm1JWS4ODUg7akJq1wjgyDU1RTAzDFli8ESmNJlMVgJgoAd6jXmzcnoA0HT9Lg==} - '@prisma/prisma-schema-wasm@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed': - resolution: {integrity: sha512-lxeu/lRSt0KbYEdsSbWpVKj3DoSqfEnB7h+ju6plR0Eg+jxEdahZRbowzd5qoiMtC57cBhtjQCD2AvewxnUyHA==} + '@prisma/prisma-schema-wasm@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e': + resolution: {integrity: sha512-s2pYG3FWY1ICGN6TVu/DrzwZWzn4oyeOZnJI8CG5fJey7i3r8EtxiBB9R9ahwL0Kqg+5KAYn37V2kVCu/9+Y/g==} - '@prisma/schema-engine-wasm@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed': - resolution: {integrity: sha512-BMdbaajhV46xPBEqwV/7pxYR67hA02ywUcKDKTIULhaofdbhCUee/O0zqoG4TmgsQZc3DUdwK7nmh2U7Ndj3fA==} + '@prisma/schema-engine-wasm@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e': + resolution: {integrity: sha512-2NEk/2meUvvpcI+Sn9DdCjKDGWRxYPnHbLI6u05ZI2TIIgk/QwdAryn+xgO9VebmWSjpQoUhaIM+KCioKU1y5w==} '@prisma/schema-files-loader@5.14.0': resolution: {integrity: sha512-n1QHR2C63dARKPZe0WPn7biybcBHzXe+BEmiHC5Drq9KPWnpmQtIfGpqm1ZKdvCZfcA5FF3wgpSMPK4LnB0obQ==} - '@prisma/schema-files-loader@6.7.0': - resolution: {integrity: sha512-JaAu/hcMAX6rEhB48wTTyTqo7BNS2Cw8ReTGdqIHXJlBHz4p1HxvoqYcmJw/WEsjUx07J0L7RgQJhhbJpo1cdQ==} + '@prisma/schema-files-loader@6.8.2': + resolution: {integrity: sha512-JRXJ08xfA1cP30kgP15AMCOKZchy2ss2oN6nSHxN745euh2tijpzvW4yCD4Q/1ZtnDkfKae45Tcpx0Ytab7RPA==} '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -2716,6 +2719,7 @@ packages: '@readme/json-schema-ref-parser@1.2.0': resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==} + deprecated: This package is no longer maintained. Please use `@apidevtools/json-schema-ref-parser` instead. '@readme/openapi-parser@2.6.0': resolution: {integrity: sha512-pyFJXezWj9WI1O+gdp95CoxfY+i+Uq3kKk4zXIFuRAZi9YnHpHOpjumWWr67wkmRTw19Hskh9spyY0Iyikf3fA==} @@ -2919,6 +2923,9 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sinclair/typebox@0.34.33': + resolution: {integrity: sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g==} + '@sindresorhus/merge-streams@2.3.0': resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} @@ -3025,6 +3032,13 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -4193,6 +4207,10 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} @@ -4383,6 +4401,15 @@ packages: supports-color: optional: true + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@2.0.0: resolution: {integrity: sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==} engines: {node: '>=4'} @@ -4584,6 +4611,13 @@ packages: electron-to-chromium@1.4.814: resolution: {integrity: sha512-GVulpHjFu1Y9ZvikvbArHmAhZXtm3wHlpjTMcXNGKl4IQ4jMQjlnz8yMQYYqdLHKi/jEL2+CBC2akWVCoIGUdw==} + elysia@1.3.1: + resolution: {integrity: sha512-En41P6cDHcHtQ0nvfsn9ayB+8ahQJqG1nzvPX8FVZjOriFK/RtZPQBtXMfZDq/AsVIk7JFZGFEtAVEmztNJVhQ==} + peerDependencies: + exact-mirror: '>= 0.0.9' + file-type: '>= 20.0.0' + typescript: '>= 5.0.0' + emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} @@ -4663,11 +4697,6 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} - esbuild-register@3.6.0: - resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} - peerDependencies: - esbuild: '>=0.12 <1' - esbuild@0.18.20: resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} engines: {node: '>=12'} @@ -4792,6 +4821,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + exact-mirror@0.1.2: + resolution: {integrity: sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw==} + peerDependencies: + '@sinclair/typebox': ^0.34.15 + peerDependenciesMeta: + '@sinclair/typebox': + optional: true + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -4889,6 +4926,9 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -4897,6 +4937,10 @@ packages: resolution: {integrity: sha512-mQ6dqz+z59on3B50IGF3ujNGbZmY1TAeLHpNfhLEeNM6Lky31w3RUlbCyqZWQs0DuZJQU4R2qDuVd9ojyzadcg==} engines: {node: '>=12.17'} + file-type@20.5.0: + resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==} + engines: {node: '>=18'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -5782,6 +5826,10 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -6682,6 +6730,10 @@ packages: pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + peek-readable@7.0.0: + resolution: {integrity: sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==} + engines: {node: '>=18'} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -7711,6 +7763,10 @@ packages: strip-literal@2.1.0: resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + strtok3@10.2.2: + resolution: {integrity: sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==} + engines: {node: '>=18'} + styled-jsx@5.1.1: resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -7917,6 +7973,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-types@6.0.0: + resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} + engines: {node: '>=14.16'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -8128,6 +8188,10 @@ packages: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} engines: {node: '>=8'} + uint8array-extras@1.4.0: + resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} + engines: {node: '>=18'} + ultrahtml@1.5.3: resolution: {integrity: sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==} @@ -10719,35 +10783,32 @@ snapshots: optionalDependencies: prisma: 6.0.0 - '@prisma/client@6.7.0(prisma@6.0.0)(typescript@5.5.2)': + '@prisma/client@6.8.2(prisma@6.0.0)(typescript@5.5.2)': optionalDependencies: prisma: 6.0.0 typescript: 5.5.2 - '@prisma/config@6.7.0': + '@prisma/config@6.8.2': dependencies: - esbuild: 0.24.0 - esbuild-register: 3.6.0(esbuild@0.24.0) - transitivePeerDependencies: - - supports-color + jiti: 2.4.2 '@prisma/debug@5.14.0': {} '@prisma/debug@6.0.0': {} - '@prisma/debug@6.7.0': {} + '@prisma/debug@6.8.2': {} - '@prisma/dmmf@6.7.0': {} + '@prisma/dmmf@6.8.2': {} - '@prisma/driver-adapter-utils@6.7.0': + '@prisma/driver-adapter-utils@6.8.2': dependencies: - '@prisma/debug': 6.7.0 + '@prisma/debug': 6.8.2 '@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': {} '@prisma/engines-version@5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e': {} - '@prisma/engines-version@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed': {} + '@prisma/engines-version@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e': {} '@prisma/engines@5.14.0': dependencies: @@ -10763,12 +10824,12 @@ snapshots: '@prisma/fetch-engine': 6.0.0 '@prisma/get-platform': 6.0.0 - '@prisma/engines@6.7.0': + '@prisma/engines@6.8.2': dependencies: - '@prisma/debug': 6.7.0 - '@prisma/engines-version': 6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed - '@prisma/fetch-engine': 6.7.0 - '@prisma/get-platform': 6.7.0 + '@prisma/debug': 6.8.2 + '@prisma/engines-version': 6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e + '@prisma/fetch-engine': 6.8.2 + '@prisma/get-platform': 6.8.2 '@prisma/fetch-engine@5.14.0': dependencies: @@ -10782,23 +10843,23 @@ snapshots: '@prisma/engines-version': 5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e '@prisma/get-platform': 6.0.0 - '@prisma/fetch-engine@6.7.0': + '@prisma/fetch-engine@6.8.2': dependencies: - '@prisma/debug': 6.7.0 - '@prisma/engines-version': 6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed - '@prisma/get-platform': 6.7.0 + '@prisma/debug': 6.8.2 + '@prisma/engines-version': 6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e + '@prisma/get-platform': 6.8.2 '@prisma/generator-helper@5.14.0': dependencies: '@prisma/debug': 5.14.0 - '@prisma/generator-helper@6.7.0': + '@prisma/generator-helper@6.8.2': dependencies: - '@prisma/debug': 6.7.0 - '@prisma/dmmf': 6.7.0 - '@prisma/generator': 6.7.0 + '@prisma/debug': 6.8.2 + '@prisma/dmmf': 6.8.2 + '@prisma/generator': 6.8.2 - '@prisma/generator@6.7.0': {} + '@prisma/generator@6.8.2': {} '@prisma/get-platform@5.14.0': dependencies: @@ -10808,9 +10869,9 @@ snapshots: dependencies: '@prisma/debug': 6.0.0 - '@prisma/get-platform@6.7.0': + '@prisma/get-platform@6.8.2': dependencies: - '@prisma/debug': 6.7.0 + '@prisma/debug': 6.8.2 '@prisma/internals@5.14.0': dependencies: @@ -10824,43 +10885,41 @@ snapshots: arg: 5.0.2 prompts: 2.4.2 - '@prisma/internals@6.7.0(typescript@5.5.2)': - dependencies: - '@prisma/config': 6.7.0 - '@prisma/debug': 6.7.0 - '@prisma/dmmf': 6.7.0 - '@prisma/driver-adapter-utils': 6.7.0 - '@prisma/engines': 6.7.0 - '@prisma/fetch-engine': 6.7.0 - '@prisma/generator': 6.7.0 - '@prisma/generator-helper': 6.7.0 - '@prisma/get-platform': 6.7.0 - '@prisma/prisma-schema-wasm': 6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed - '@prisma/schema-engine-wasm': 6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed - '@prisma/schema-files-loader': 6.7.0 + '@prisma/internals@6.8.2(typescript@5.5.2)': + dependencies: + '@prisma/config': 6.8.2 + '@prisma/debug': 6.8.2 + '@prisma/dmmf': 6.8.2 + '@prisma/driver-adapter-utils': 6.8.2 + '@prisma/engines': 6.8.2 + '@prisma/fetch-engine': 6.8.2 + '@prisma/generator': 6.8.2 + '@prisma/generator-helper': 6.8.2 + '@prisma/get-platform': 6.8.2 + '@prisma/prisma-schema-wasm': 6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e + '@prisma/schema-engine-wasm': 6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e + '@prisma/schema-files-loader': 6.8.2 arg: 5.0.2 prompts: 2.4.2 optionalDependencies: typescript: 5.5.2 - transitivePeerDependencies: - - supports-color '@prisma/prisma-schema-wasm@5.14.0-17.56ca112d5a19c9925b53af75c3c6b7ada97f9f85': {} '@prisma/prisma-schema-wasm@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48': {} - '@prisma/prisma-schema-wasm@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed': {} + '@prisma/prisma-schema-wasm@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e': {} - '@prisma/schema-engine-wasm@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed': {} + '@prisma/schema-engine-wasm@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e': {} '@prisma/schema-files-loader@5.14.0': dependencies: '@prisma/prisma-schema-wasm': 5.14.0-17.56ca112d5a19c9925b53af75c3c6b7ada97f9f85 fs-extra: 11.1.1 - '@prisma/schema-files-loader@6.7.0': + '@prisma/schema-files-loader@6.8.2': dependencies: - '@prisma/prisma-schema-wasm': 6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed + '@prisma/prisma-schema-wasm': 6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e fs-extra: 11.3.0 '@protobufjs/aspromise@1.1.2': {} @@ -11151,6 +11210,9 @@ snapshots: '@sinclair/typebox@0.27.8': {} + '@sinclair/typebox@0.34.33': + optional: true + '@sindresorhus/merge-streams@2.3.0': {} '@sinonjs/commons@3.0.1': @@ -11278,6 +11340,16 @@ snapshots: react: 18.2.0 react-dom: 18.3.1(react@18.2.0) + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.1 + fflate: 0.8.2 + token-types: 6.0.0 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + '@tootallnate/once@2.0.0': {} '@trpc/client@10.45.2(@trpc/server@10.45.2)': @@ -12673,6 +12745,8 @@ snapshots: cookie@0.6.0: {} + cookie@1.0.2: {} + cookiejar@2.1.4: {} copy-anything@3.0.5: @@ -12882,6 +12956,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.4.1: + dependencies: + ms: 2.1.3 + decamelize@2.0.0: dependencies: xregexp: 4.0.0 @@ -13057,6 +13135,17 @@ snapshots: electron-to-chromium@1.4.814: {} + elysia@1.3.1(exact-mirror@0.1.2(@sinclair/typebox@0.34.33))(file-type@20.5.0)(typescript@5.5.2): + dependencies: + cookie: 1.0.2 + exact-mirror: 0.1.2(@sinclair/typebox@0.34.33) + fast-decode-uri-component: 1.0.1 + file-type: 20.5.0 + typescript: 5.5.2 + optionalDependencies: + '@sinclair/typebox': 0.34.33 + openapi-types: 12.1.3 + emittery@0.13.1: {} emoji-regex@8.0.0: {} @@ -13188,13 +13277,6 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - esbuild-register@3.6.0(esbuild@0.24.0): - dependencies: - debug: 4.3.5 - esbuild: 0.24.0 - transitivePeerDependencies: - - supports-color - esbuild@0.18.20: optionalDependencies: '@esbuild/android-arm': 0.18.20 @@ -13416,6 +13498,10 @@ snapshots: events@3.3.0: {} + exact-mirror@0.1.2(@sinclair/typebox@0.34.33): + optionalDependencies: + '@sinclair/typebox': 0.34.33 + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -13595,6 +13681,8 @@ snapshots: dependencies: pend: 1.2.0 + fflate@0.8.2: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -13604,6 +13692,15 @@ snapshots: array-back: 6.2.2 glob: 7.2.3 + file-type@20.5.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.2.2 + token-types: 6.0.0 + uint8array-extras: 1.4.0 + transitivePeerDependencies: + - supports-color + file-uri-to-path@1.0.0: {} fill-keys@1.0.2: @@ -14731,6 +14828,8 @@ snapshots: jiti@1.21.6: {} + jiti@2.4.2: {} + joycon@3.1.1: {} js-tokens@4.0.0: {} @@ -15856,6 +15955,8 @@ snapshots: pathval@1.1.1: {} + peek-readable@7.0.0: {} + pend@1.2.0: {} perfect-debounce@1.0.0: {} @@ -16932,6 +17033,11 @@ snapshots: dependencies: js-tokens: 9.0.0 + strtok3@10.2.2: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 7.0.0 + styled-jsx@5.1.1(@babel/core@7.24.7)(react@18.2.0): dependencies: client-only: 0.0.1 @@ -17169,6 +17275,11 @@ snapshots: toidentifier@1.0.1: {} + token-types@6.0.0: + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + totalist@3.0.1: {} tough-cookie@4.1.4: @@ -17389,6 +17500,8 @@ snapshots: dependencies: '@lukeed/csprng': 1.1.0 + uint8array-extras@1.4.0: {} + ultrahtml@1.5.3: {} unbox-primitive@1.0.2: diff --git a/script/test-scaffold.ts b/script/test-scaffold.ts index 4d5651ae2..c05b2c0f8 100644 --- a/script/test-scaffold.ts +++ b/script/test-scaffold.ts @@ -19,6 +19,6 @@ function run(cmd: string) { } run('npm init -y'); -run('npm i --no-audit --no-fund typescript prisma@6.7.x @prisma/client@6.7.x zod@^3.22.4 decimal.js @types/node'); +run('npm i --no-audit --no-fund typescript prisma@6.8.x @prisma/client@6.8.x zod@^3.22.4 decimal.js @types/node'); console.log('Test scaffold setup complete.'); diff --git a/tests/integration/test-run/package.json b/tests/integration/test-run/package.json index 8b915820e..5eabb95cd 100644 --- a/tests/integration/test-run/package.json +++ b/tests/integration/test-run/package.json @@ -10,9 +10,9 @@ "author": "", "license": "ISC", "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@zenstackhq/runtime": "file:../../../packages/runtime/dist", - "prisma": "6.7.x", + "prisma": "6.8.x", "react": "^18.2.0", "swr": "^1.3.0", "typescript": "^4.9.3", diff --git a/tests/integration/tests/cli/plugins.test.ts b/tests/integration/tests/cli/plugins.test.ts index 525ec4550..25a4fa117 100644 --- a/tests/integration/tests/cli/plugins.test.ts +++ b/tests/integration/tests/cli/plugins.test.ts @@ -75,7 +75,7 @@ describe('CLI Plugins Tests', () => { 'swr', '@tanstack/react-query@5.56.x', '@trpc/server', - '@prisma/client@6.7.x', + '@prisma/client@6.8.x', `${path.join(__dirname, '../../../../.build/zenstackhq-language-' + ver + '.tgz')}`, `${path.join(__dirname, '../../../../.build/zenstackhq-sdk-' + ver + '.tgz')}`, `${path.join(__dirname, '../../../../.build/zenstackhq-runtime-' + ver + '.tgz')}`, @@ -85,7 +85,7 @@ describe('CLI Plugins Tests', () => { const devDepPkgs = [ 'typescript', '@types/react', - 'prisma@6.7.x', + 'prisma@6.8.x', `${path.join(__dirname, '../../../../.build/zenstack-' + ver + '.tgz')}`, `${path.join(__dirname, '../../../../.build/zenstackhq-tanstack-query-' + ver + '.tgz')}`, `${path.join(__dirname, '../../../../.build/zenstackhq-swr-' + ver + '.tgz')}`, diff --git a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts index 1f7a40129..544198c32 100644 --- a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts +++ b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts @@ -476,6 +476,8 @@ describe('Polymorphism Test', () => { it('update simple', async () => { const { db, videoWithOwner: video } = await setup(); + const read = await db.ratedVideo.findUnique({ where: { id: video.id } }); + // update with concrete let updated = await db.ratedVideo.update({ where: { id: video.id }, @@ -484,6 +486,7 @@ describe('Polymorphism Test', () => { }); expect(updated.rating).toBe(200); expect(updated.owner).toBeTruthy(); + expect(updated.updatedAt.getTime()).toBeGreaterThan(read.updatedAt.getTime()); // update with base updated = await db.video.update({ @@ -613,17 +616,18 @@ describe('Polymorphism Test', () => { }); // updateMany with filter - await expect( - db.user.update({ - where: { id: user.id }, - data: { - ratedVideos: { updateMany: { where: { duration: 1 }, data: { rating: 333 } } }, - }, - include: { ratedVideos: true }, - }) - ).resolves.toMatchObject({ + const read = await db.ratedVideo.findFirst({ where: { duration: 1 } }); + const r = await db.user.update({ + where: { id: user.id }, + data: { + ratedVideos: { updateMany: { where: { duration: 1 }, data: { rating: 333 } } }, + }, + include: { ratedVideos: true }, + }); + expect(r).toMatchObject({ ratedVideos: expect.arrayContaining([expect.objectContaining({ rating: 333 })]), }); + expect(r.ratedVideos[0].updatedAt.getTime()).toBeGreaterThan(read.updatedAt.getTime()); // updateMany without filter await expect( @@ -1025,22 +1029,23 @@ describe('Polymorphism Test', () => { ).rejects.toThrow('is a delegate'); // update - await expect( - db.ratedVideo.upsert({ - where: { id: video.id }, - create: { - viewCount: 1, - duration: 300, - url: 'xyz', - rating: 100, - owner: { connect: { id: user.id } }, - }, - update: { duration: 200 }, - }) - ).resolves.toMatchObject({ + const read = await db.ratedVideo.findUnique({ where: { id: video.id } }); + const r = await db.ratedVideo.upsert({ + where: { id: video.id }, + create: { + viewCount: 1, + duration: 300, + url: 'xyz', + rating: 100, + owner: { connect: { id: user.id } }, + }, + update: { duration: 200 }, + }); + expect(r).toMatchObject({ id: video.id, duration: 200, }); + expect(r.updatedAt.getTime()).toBeGreaterThan(read.updatedAt.getTime()); // create const created = await db.ratedVideo.upsert({ @@ -1052,7 +1057,7 @@ describe('Polymorphism Test', () => { expect(created.duration).toBe(300); }); - it('delete', async () => { + it('delete simple', async () => { let { db, user, video: ratedVideo } = await setup(); let deleted = await db.ratedVideo.delete({ @@ -1101,6 +1106,55 @@ describe('Polymorphism Test', () => { await expect(db.asset.findUnique({ where: { id: ratedVideo.id } })).resolves.toBeNull(); }); + it('delete cascade', async () => { + const { prisma, enhance } = await loadSchema( + ` + model Base { + id Int @id @default(autoincrement()) + type String + @@delegate(type) + } + + model List extends Base { + name String + items Item[] + } + + model Item extends Base { + name String + list List @relation(fields: [listId], references: [id], onDelete: Cascade) + listId Int + content ItemContent? + } + + model ItemContent extends Base { + name String + item Item @relation(fields: [itemId], references: [id], onDelete: Cascade) + itemId Int @unique + } +`, + { enhancements: ['delegate'], logPrismaQuery: true } + ); + + const db = enhance(); + await db.list.create({ + data: { + id: 1, + name: 'list', + items: { + create: [{ id: 2, name: 'item1', content: { create: { id: 3, name: 'content1' } } }], + }, + }, + }); + + const r = await db.list.delete({ where: { id: 1 }, include: { items: { include: { content: true } } } }); + expect(r).toMatchObject({ items: [{ id: 2 }] }); + await expect(db.item.findUnique({ where: { id: 2 } })).toResolveNull(); + await expect(prisma.base.findUnique({ where: { id: 2 } })).toResolveNull(); + await expect(db.itemContent.findUnique({ where: { id: 3 } })).toResolveNull(); + await expect(prisma.base.findUnique({ where: { id: 3 } })).toResolveNull(); + }); + it('deleteMany', async () => { const { enhance } = await loadSchema(schema, { enhancements: ['delegate'] }); const db = enhance(); diff --git a/tests/integration/tests/enhancements/with-delegate/plugin-interaction.test.ts b/tests/integration/tests/enhancements/with-delegate/plugin-interaction.test.ts index 8247c2e45..aceb50932 100644 --- a/tests/integration/tests/enhancements/with-delegate/plugin-interaction.test.ts +++ b/tests/integration/tests/enhancements/with-delegate/plugin-interaction.test.ts @@ -65,6 +65,7 @@ describe('Polymorphic Plugin Interaction Test', () => { id: 1, assetType: 'video', createdAt: new Date(), + updatedAt: new Date(), viewCount: 100, }) ).toBeTruthy(); @@ -74,6 +75,7 @@ describe('Polymorphic Plugin Interaction Test', () => { id: 1, assetType: 'video', createdAt: new Date(), + updatedAt: new Date(), viewCount: 100, videoType: 'ratedVideo', // should be stripped }).videoType @@ -87,6 +89,7 @@ describe('Polymorphic Plugin Interaction Test', () => { duration: 100, url: 'http://example.com', createdAt: new Date(), + updatedAt: new Date(), viewCount: 100, }) ).toBeTruthy(); @@ -98,6 +101,7 @@ describe('Polymorphic Plugin Interaction Test', () => { videoType: 'ratedVideo', url: 'http://example.com', createdAt: new Date(), + updatedAt: new Date(), viewCount: 100, }) ).toThrow('duration'); diff --git a/tests/integration/tests/enhancements/with-delegate/utils.ts b/tests/integration/tests/enhancements/with-delegate/utils.ts index 41700bd4b..23bae33db 100644 --- a/tests/integration/tests/enhancements/with-delegate/utils.ts +++ b/tests/integration/tests/enhancements/with-delegate/utils.ts @@ -12,6 +12,7 @@ model User { model Asset { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt viewCount Int @default(0) owner User? @relation(fields: [ownerId], references: [id]) ownerId Int? diff --git a/tests/integration/tests/frameworks/nextjs/test-project/package.json b/tests/integration/tests/frameworks/nextjs/test-project/package.json index 32cd65994..81fcef311 100644 --- a/tests/integration/tests/frameworks/nextjs/test-project/package.json +++ b/tests/integration/tests/frameworks/nextjs/test-project/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@types/node": "18.11.18", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", @@ -26,6 +26,6 @@ "@zenstackhq/swr": "../../../../../../../packages/plugins/swr/dist" }, "devDependencies": { - "prisma": "6.7.x" + "prisma": "6.8.x" } } diff --git a/tests/integration/tests/frameworks/trpc/test-project/package.json b/tests/integration/tests/frameworks/trpc/test-project/package.json index b1f5c9a83..43170787e 100644 --- a/tests/integration/tests/frameworks/trpc/test-project/package.json +++ b/tests/integration/tests/frameworks/trpc/test-project/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@prisma/client": "6.7.x", + "@prisma/client": "6.8.x", "@tanstack/react-query": "^4.22.4", "@trpc/client": "^10.34.0", "@trpc/next": "^10.34.0", @@ -31,6 +31,6 @@ "@zenstackhq/trpc": "../../../../../../../packages/plugins/trpc/dist" }, "devDependencies": { - "prisma": "6.7.x" + "prisma": "6.8.x" } } diff --git a/tests/regression/tests/issue-2106.test.ts b/tests/regression/tests/issue-2106.test.ts new file mode 100644 index 000000000..c81347918 --- /dev/null +++ b/tests/regression/tests/issue-2106.test.ts @@ -0,0 +1,19 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 2106', () => { + it('regression', async () => { + const { enhance } = await loadSchema( + ` + model User { + id Int @id + age BigInt + @@allow('all', true) + } + `, + { logPrismaQuery: true } + ); + + const db = enhance(); + await expect(db.user.create({ data: { id: 1, age: 1n } })).toResolveTruthy(); + }); +}); diff --git a/tests/regression/tests/issue-2117.test.ts b/tests/regression/tests/issue-2117.test.ts new file mode 100644 index 000000000..46be7c195 --- /dev/null +++ b/tests/regression/tests/issue-2117.test.ts @@ -0,0 +1,43 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 2117', () => { + it('regression', async () => { + const { prisma, enhanceRaw, prismaModule } = await loadSchema( + ` + model User { + uuid String @id + email String @unique @deny('read', auth().uuid != this.uuid) + username String @unique + @@allow('all', true) + } + ` + ); + + const extPrisma = prisma.$extends( + prismaModule.defineExtension({ + name: 'urls-extension', + result: { + user: { + pageUrl: { + needs: { username: true }, + compute: () => `foo`, + }, + }, + }, + }) + ); + + const db = enhanceRaw(extPrisma, { user: { uuid: '1' } }, { logPrismaQuery: true }); + await db.user.create({ data: { uuid: '1', email: 'a@b.com', username: 'a' } }); + await expect(db.user.findFirst()).resolves.toMatchObject({ + uuid: '1', + email: 'a@b.com', + username: 'a', + pageUrl: 'foo', + }); + const r = await db.user.findFirst({ select: { email: true } }); + expect(r.email).toBeTruthy(); + expect(r.uuid).toBeUndefined(); + expect(r.pageUrl).toBeUndefined(); + }); +});