diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 21b7811..74dbddf 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ "tamasfe.even-better-toml", "oven.bun-vscode", "oxc.oxc-vscode", - "vitest.explorer" + "vitest.explorer", + "zenstack.zenstack-v3" ] } } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 852fb4d..61dadd3 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "tamasfe.even-better-toml", "oven.bun-vscode", "oxc.oxc-vscode", - "vitest.explorer" + "vitest.explorer", + "zenstack.zenstack-v3" ] } \ No newline at end of file diff --git a/package.json b/package.json index 65624f0..c4fb4f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "workspace", "type": "module", - "version": "1.0.2", + "version": "1.0.3", "private": "true", "license": "MIT", "scripts": { diff --git a/packages/cache/package.json b/packages/cache/package.json index d5208b7..819491f 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,20 +1,23 @@ { "name": "@visualbravo/zenstack-cache", - "version": "1.0.2", + "version": "1.0.3", + "keywords": [ + "accelerate", + "cache", + "caching", + "orm", + "prisma", + "redis", + "self-hosted", + "typescript", + "zenstack" + ], "license": "MIT", "repository": "github:visualbravo/zenstack-cache", "type": "module", "main": "./dist/index.cjs", "module": "./dist/index.mjs", "types": "./dist/index.d.cts", - "keywords": [ - "zenstack", - "cache", - "caching", - "prisma", - "accelerate", - "orm" - ], "exports": { ".": { "@zenstack-cache/source": "./src/index.ts", diff --git a/packages/cache/src/plugin.ts b/packages/cache/src/plugin.ts index 7e87a2b..79ef3a6 100644 --- a/packages/cache/src/plugin.ts +++ b/packages/cache/src/plugin.ts @@ -56,13 +56,28 @@ export function defineCachePlugin(pluginOptions: CachePluginOptions) { }, }, - onQuery: async ({ args, model, operation, proceed }) => { + onQuery: async ({ args, model, operation, proceed, client }) => { if (args && 'cache' in args) { - const json = stableHash({ - args, - model, - operation, - }) + let json: string + + if (client.$auth) { + const userId = Object.keys(client.$auth) + .filter(key => client.$schema.models[client.$schema.authType!]!.idFields.includes(key)) + .join('_') + + json = stableHash({ + args, + model, + operation, + userId, + }) + } else { + json = stableHash({ + args, + model, + operation, + }) + } if (!json) { throw new Error( @@ -114,4 +129,4 @@ export function defineCachePlugin(pluginOptions: CachePluginOptions) { return proceed(args) }, }) -} \ No newline at end of file +} diff --git a/packages/cache/tests/memory.test.ts b/packages/cache/tests/memory.test.ts index df657c7..881aaa5 100644 --- a/packages/cache/tests/memory.test.ts +++ b/packages/cache/tests/memory.test.ts @@ -4,7 +4,7 @@ import { defineCachePlugin } from '../src' import { SqliteDialect } from 'kysely' import SQLite from 'better-sqlite3' import { MemoryCacheProvider } from '../src/providers/memory' -import { schema } from './schemas/basic' +import { schema } from './schemas/basic/schema' describe('Cache plugin (memory)', () => { let db: ClientContract @@ -1095,4 +1095,62 @@ describe('Cache plugin (memory)', () => { }), ).rejects.toThrow('Invalid findMany') }) + + it('caches auth separately', async () => { + let extDb = db.$use( + defineCachePlugin({ + provider: new MemoryCacheProvider(), + }), + ) + + await expect( + extDb.user.exists({ + where: { + id: '1', + }, + + cache: {}, + }), + ).resolves.toBe(false) + + await extDb.user.create({ + data: { + createdAt: new Date(), + email: 'test@email.com', + id: '1', + name: 'test', + role: 'USER', + updatedAt: new Date(), + }, + }) + + await expect( + extDb.user.exists({ + where: { + id: '1', + }, + + cache: {}, + }), + ).resolves.toBe(false) + + extDb = extDb.$setAuth({ + createdAt: new Date(), + email: 'test@email.com', + id: '1', + name: 'test', + role: 'USER', + updatedAt: new Date(), + }) + + await expect( + extDb.user.exists({ + where: { + id: '1', + }, + + cache: {}, + }), + ).resolves.toBe(true) + }) }) diff --git a/packages/cache/tests/redis.test.ts b/packages/cache/tests/redis.test.ts index 5d466f6..a4a8c71 100644 --- a/packages/cache/tests/redis.test.ts +++ b/packages/cache/tests/redis.test.ts @@ -4,7 +4,7 @@ import { defineCachePlugin } from '../src' import { SqliteDialect } from 'kysely' import SQLite from 'better-sqlite3' import { RedisCacheProvider } from '../src/providers/redis' -import { schema } from './schemas/basic' +import { schema } from './schemas/basic/schema' import { Redis, Pipeline } from 'ioredis' const expire = vi.spyOn(Pipeline.prototype, 'expire') @@ -1131,4 +1131,64 @@ describe('Cache plugin (redis)', () => { await expect(redis.ttl('zenstack:cache:tag:test2')).resolves.toBeCloseTo(80, 2) await expect(redis.ttl('zenstack:cache:tag:test3')).resolves.toBeCloseTo(80, 2) }) + + it('caches auth separately', async () => { + let extDb = db.$use( + defineCachePlugin({ + provider: new RedisCacheProvider({ + url: process.env['REDIS_URL'] as string, + }), + }), + ) + + await expect( + extDb.user.exists({ + where: { + id: '1', + }, + + cache: {}, + }), + ).resolves.toBe(false) + + await extDb.user.create({ + data: { + createdAt: new Date(), + email: 'test@email.com', + id: '1', + name: 'test', + role: 'USER', + updatedAt: new Date(), + }, + }) + + await expect( + extDb.user.exists({ + where: { + id: '1', + }, + + cache: {}, + }), + ).resolves.toBe(false) + + extDb = extDb.$setAuth({ + createdAt: new Date(), + email: 'test@email.com', + id: '1', + name: 'test', + role: 'USER', + updatedAt: new Date(), + }) + + await expect( + extDb.user.exists({ + where: { + id: '1', + }, + + cache: {}, + }), + ).resolves.toBe(true) + }) }) diff --git a/packages/cache/tests/schemas/basic/input.ts b/packages/cache/tests/schemas/basic/input.ts new file mode 100644 index 0000000..b269c2d --- /dev/null +++ b/packages/cache/tests/schemas/basic/input.ts @@ -0,0 +1,131 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaType as $Schema } from './schema' +import type { + FindManyArgs as $FindManyArgs, + FindUniqueArgs as $FindUniqueArgs, + FindFirstArgs as $FindFirstArgs, + ExistsArgs as $ExistsArgs, + CreateArgs as $CreateArgs, + CreateManyArgs as $CreateManyArgs, + CreateManyAndReturnArgs as $CreateManyAndReturnArgs, + UpdateArgs as $UpdateArgs, + UpdateManyArgs as $UpdateManyArgs, + UpdateManyAndReturnArgs as $UpdateManyAndReturnArgs, + UpsertArgs as $UpsertArgs, + DeleteArgs as $DeleteArgs, + DeleteManyArgs as $DeleteManyArgs, + CountArgs as $CountArgs, + AggregateArgs as $AggregateArgs, + GroupByArgs as $GroupByArgs, + WhereInput as $WhereInput, + SelectInput as $SelectInput, + IncludeInput as $IncludeInput, + OmitInput as $OmitInput, + QueryOptions as $QueryOptions, +} from '@zenstackhq/orm' +import type { + SimplifiedPlainResult as $Result, + SelectIncludeOmit as $SelectIncludeOmit, +} from '@zenstackhq/orm' +export type UserFindManyArgs = $FindManyArgs<$Schema, 'User'> +export type UserFindUniqueArgs = $FindUniqueArgs<$Schema, 'User'> +export type UserFindFirstArgs = $FindFirstArgs<$Schema, 'User'> +export type UserExistsArgs = $ExistsArgs<$Schema, 'User'> +export type UserCreateArgs = $CreateArgs<$Schema, 'User'> +export type UserCreateManyArgs = $CreateManyArgs<$Schema, 'User'> +export type UserCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, 'User'> +export type UserUpdateArgs = $UpdateArgs<$Schema, 'User'> +export type UserUpdateManyArgs = $UpdateManyArgs<$Schema, 'User'> +export type UserUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, 'User'> +export type UserUpsertArgs = $UpsertArgs<$Schema, 'User'> +export type UserDeleteArgs = $DeleteArgs<$Schema, 'User'> +export type UserDeleteManyArgs = $DeleteManyArgs<$Schema, 'User'> +export type UserCountArgs = $CountArgs<$Schema, 'User'> +export type UserAggregateArgs = $AggregateArgs<$Schema, 'User'> +export type UserGroupByArgs = $GroupByArgs<$Schema, 'User'> +export type UserWhereInput = $WhereInput<$Schema, 'User'> +export type UserSelect = $SelectInput<$Schema, 'User'> +export type UserInclude = $IncludeInput<$Schema, 'User'> +export type UserOmit = $OmitInput<$Schema, 'User'> +export type UserGetPayload< + Args extends $SelectIncludeOmit<$Schema, 'User', true>, + Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>, +> = $Result<$Schema, 'User', Args, Options> +export type PostFindManyArgs = $FindManyArgs<$Schema, 'Post'> +export type PostFindUniqueArgs = $FindUniqueArgs<$Schema, 'Post'> +export type PostFindFirstArgs = $FindFirstArgs<$Schema, 'Post'> +export type PostExistsArgs = $ExistsArgs<$Schema, 'Post'> +export type PostCreateArgs = $CreateArgs<$Schema, 'Post'> +export type PostCreateManyArgs = $CreateManyArgs<$Schema, 'Post'> +export type PostCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, 'Post'> +export type PostUpdateArgs = $UpdateArgs<$Schema, 'Post'> +export type PostUpdateManyArgs = $UpdateManyArgs<$Schema, 'Post'> +export type PostUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, 'Post'> +export type PostUpsertArgs = $UpsertArgs<$Schema, 'Post'> +export type PostDeleteArgs = $DeleteArgs<$Schema, 'Post'> +export type PostDeleteManyArgs = $DeleteManyArgs<$Schema, 'Post'> +export type PostCountArgs = $CountArgs<$Schema, 'Post'> +export type PostAggregateArgs = $AggregateArgs<$Schema, 'Post'> +export type PostGroupByArgs = $GroupByArgs<$Schema, 'Post'> +export type PostWhereInput = $WhereInput<$Schema, 'Post'> +export type PostSelect = $SelectInput<$Schema, 'Post'> +export type PostInclude = $IncludeInput<$Schema, 'Post'> +export type PostOmit = $OmitInput<$Schema, 'Post'> +export type PostGetPayload< + Args extends $SelectIncludeOmit<$Schema, 'Post', true>, + Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>, +> = $Result<$Schema, 'Post', Args, Options> +export type CommentFindManyArgs = $FindManyArgs<$Schema, 'Comment'> +export type CommentFindUniqueArgs = $FindUniqueArgs<$Schema, 'Comment'> +export type CommentFindFirstArgs = $FindFirstArgs<$Schema, 'Comment'> +export type CommentExistsArgs = $ExistsArgs<$Schema, 'Comment'> +export type CommentCreateArgs = $CreateArgs<$Schema, 'Comment'> +export type CommentCreateManyArgs = $CreateManyArgs<$Schema, 'Comment'> +export type CommentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, 'Comment'> +export type CommentUpdateArgs = $UpdateArgs<$Schema, 'Comment'> +export type CommentUpdateManyArgs = $UpdateManyArgs<$Schema, 'Comment'> +export type CommentUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, 'Comment'> +export type CommentUpsertArgs = $UpsertArgs<$Schema, 'Comment'> +export type CommentDeleteArgs = $DeleteArgs<$Schema, 'Comment'> +export type CommentDeleteManyArgs = $DeleteManyArgs<$Schema, 'Comment'> +export type CommentCountArgs = $CountArgs<$Schema, 'Comment'> +export type CommentAggregateArgs = $AggregateArgs<$Schema, 'Comment'> +export type CommentGroupByArgs = $GroupByArgs<$Schema, 'Comment'> +export type CommentWhereInput = $WhereInput<$Schema, 'Comment'> +export type CommentSelect = $SelectInput<$Schema, 'Comment'> +export type CommentInclude = $IncludeInput<$Schema, 'Comment'> +export type CommentOmit = $OmitInput<$Schema, 'Comment'> +export type CommentGetPayload< + Args extends $SelectIncludeOmit<$Schema, 'Comment', true>, + Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>, +> = $Result<$Schema, 'Comment', Args, Options> +export type ProfileFindManyArgs = $FindManyArgs<$Schema, 'Profile'> +export type ProfileFindUniqueArgs = $FindUniqueArgs<$Schema, 'Profile'> +export type ProfileFindFirstArgs = $FindFirstArgs<$Schema, 'Profile'> +export type ProfileExistsArgs = $ExistsArgs<$Schema, 'Profile'> +export type ProfileCreateArgs = $CreateArgs<$Schema, 'Profile'> +export type ProfileCreateManyArgs = $CreateManyArgs<$Schema, 'Profile'> +export type ProfileCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, 'Profile'> +export type ProfileUpdateArgs = $UpdateArgs<$Schema, 'Profile'> +export type ProfileUpdateManyArgs = $UpdateManyArgs<$Schema, 'Profile'> +export type ProfileUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, 'Profile'> +export type ProfileUpsertArgs = $UpsertArgs<$Schema, 'Profile'> +export type ProfileDeleteArgs = $DeleteArgs<$Schema, 'Profile'> +export type ProfileDeleteManyArgs = $DeleteManyArgs<$Schema, 'Profile'> +export type ProfileCountArgs = $CountArgs<$Schema, 'Profile'> +export type ProfileAggregateArgs = $AggregateArgs<$Schema, 'Profile'> +export type ProfileGroupByArgs = $GroupByArgs<$Schema, 'Profile'> +export type ProfileWhereInput = $WhereInput<$Schema, 'Profile'> +export type ProfileSelect = $SelectInput<$Schema, 'Profile'> +export type ProfileInclude = $IncludeInput<$Schema, 'Profile'> +export type ProfileOmit = $OmitInput<$Schema, 'Profile'> +export type ProfileGetPayload< + Args extends $SelectIncludeOmit<$Schema, 'Profile', true>, + Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>, +> = $Result<$Schema, 'Profile', Args, Options> diff --git a/packages/cache/tests/schemas/basic/models.ts b/packages/cache/tests/schemas/basic/models.ts new file mode 100644 index 0000000..32dd370 --- /dev/null +++ b/packages/cache/tests/schemas/basic/models.ts @@ -0,0 +1,19 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { schema as $schema, type SchemaType as $Schema } from './schema' +import { + type ModelResult as $ModelResult, + type TypeDefResult as $TypeDefResult, +} from '@zenstackhq/orm' +export type User = $ModelResult<$Schema, 'User'> +export type Post = $ModelResult<$Schema, 'Post'> +export type Comment = $ModelResult<$Schema, 'Comment'> +export type Profile = $ModelResult<$Schema, 'Profile'> +export type CommonFields = $TypeDefResult<$Schema, 'CommonFields'> +export const Role = $schema.enums.Role.values +export type Role = (typeof Role)[keyof typeof Role] diff --git a/packages/cache/tests/schemas/basic.ts b/packages/cache/tests/schemas/basic/schema.ts similarity index 78% rename from packages/cache/tests/schemas/basic.ts rename to packages/cache/tests/schemas/basic/schema.ts index e312ea9..dcb7372 100644 --- a/packages/cache/tests/schemas/basic.ts +++ b/packages/cache/tests/schemas/basic/schema.ts @@ -75,36 +75,6 @@ export class SchemaType implements SchemaDef { optional: true, }, }, - attributes: [ - { - name: '@@allow', - args: [ - { name: 'operation', value: ExpressionUtils.literal('all') }, - { - name: 'condition', - value: ExpressionUtils.binary( - ExpressionUtils.member(ExpressionUtils.call('auth'), ['id']), - '==', - ExpressionUtils.field('id'), - ), - }, - ], - }, - { - name: '@@allow', - args: [ - { name: 'operation', value: ExpressionUtils.literal('read') }, - { - name: 'condition', - value: ExpressionUtils.binary( - ExpressionUtils.call('auth'), - '!=', - ExpressionUtils._null(), - ), - }, - ], - }, - ], idFields: ['id'], uniqueFields: { id: { type: 'String' }, @@ -164,9 +134,12 @@ export class SchemaType implements SchemaDef { args: [ { name: 'fields', - value: ExpressionUtils.array([ExpressionUtils.field('authorId')]), + value: ExpressionUtils.array('String', [ExpressionUtils.field('authorId')]), + }, + { + name: 'references', + value: ExpressionUtils.array('String', [ExpressionUtils.field('id')]), }, - { name: 'references', value: ExpressionUtils.array([ExpressionUtils.field('id')]) }, { name: 'onUpdate', value: ExpressionUtils.literal('Cascade') }, { name: 'onDelete', value: ExpressionUtils.literal('Cascade') }, ], @@ -192,43 +165,6 @@ export class SchemaType implements SchemaDef { relation: { opposite: 'post' }, }, }, - attributes: [ - { - name: '@@deny', - args: [ - { name: 'operation', value: ExpressionUtils.literal('all') }, - { - name: 'condition', - value: ExpressionUtils.binary( - ExpressionUtils.call('auth'), - '==', - ExpressionUtils._null(), - ), - }, - ], - }, - { - name: '@@allow', - args: [ - { name: 'operation', value: ExpressionUtils.literal('all') }, - { - name: 'condition', - value: ExpressionUtils.binary( - ExpressionUtils.member(ExpressionUtils.call('auth'), ['id']), - '==', - ExpressionUtils.field('authorId'), - ), - }, - ], - }, - { - name: '@@allow', - args: [ - { name: 'operation', value: ExpressionUtils.literal('read') }, - { name: 'condition', value: ExpressionUtils.field('published') }, - ], - }, - ], idFields: ['id'], uniqueFields: { id: { type: 'String' }, @@ -273,8 +209,14 @@ export class SchemaType implements SchemaDef { { name: '@relation', args: [ - { name: 'fields', value: ExpressionUtils.array([ExpressionUtils.field('postId')]) }, - { name: 'references', value: ExpressionUtils.array([ExpressionUtils.field('id')]) }, + { + name: 'fields', + value: ExpressionUtils.array('String', [ExpressionUtils.field('postId')]), + }, + { + name: 'references', + value: ExpressionUtils.array('String', [ExpressionUtils.field('id')]), + }, { name: 'onUpdate', value: ExpressionUtils.literal('Cascade') }, { name: 'onDelete', value: ExpressionUtils.literal('Cascade') }, ], @@ -344,8 +286,14 @@ export class SchemaType implements SchemaDef { { name: '@relation', args: [ - { name: 'fields', value: ExpressionUtils.array([ExpressionUtils.field('userId')]) }, - { name: 'references', value: ExpressionUtils.array([ExpressionUtils.field('id')]) }, + { + name: 'fields', + value: ExpressionUtils.array('String', [ExpressionUtils.field('userId')]), + }, + { + name: 'references', + value: ExpressionUtils.array('String', [ExpressionUtils.field('id')]), + }, { name: 'onUpdate', value: ExpressionUtils.literal('Cascade') }, { name: 'onDelete', value: ExpressionUtils.literal('Cascade') }, ], diff --git a/packages/cache/tests/schemas/basic/schema.zmodel b/packages/cache/tests/schemas/basic/schema.zmodel new file mode 100644 index 0000000..e79e750 --- /dev/null +++ b/packages/cache/tests/schemas/basic/schema.zmodel @@ -0,0 +1,47 @@ +datasource db { + provider = "sqlite" + url = "file:./dev.db" +} + +enum Role { + ADMIN + USER +} + +type CommonFields { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt() +} + +model User with CommonFields { + email String @unique + name String? + password String @ignore + role Role @default(USER) + posts Post[] + profile Profile? + meta Json? +} + +model Post with CommonFields { + title String + content String? + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id], onUpdate: Cascade, onDelete: Cascade) + authorId String + comments Comment[] +} + +model Comment with CommonFields { + content String + post Post? @relation(fields: [postId], references: [id], onUpdate: Cascade, onDelete: Cascade) + postId String? +} + +model Profile with CommonFields { + bio String + age Int? + user User? @relation(fields: [userId], references: [id], onUpdate: Cascade, onDelete: Cascade) + userId String? @unique +} \ No newline at end of file diff --git a/packages/config/package.json b/packages/config/package.json index c032ece..23a6ce7 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@zenstack-cache/config", "type": "module", - "version": "1.0.2", + "version": "1.0.3", "private": true, "exports": { "./ts": "./ts.json"