diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b48bc73b8..18dff12e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ I want to thank you first for considering contributing to ZenStack 🙏🏻. It's people like you who make ZenStack a better toolkit that benefits more developers! -Before you start working on anything major, please make sure to open an issue or discuss with us in our [Discord](https://discord.gg/Ykhr738dUe) first. This will help ensure your work aligns with the project's goals and avoid duplication of effort. +Before you start working on anything major, please make sure to create a topic in the [feature-work](https://discord.com/channels/1035538056146595961/1458658287015952498) discord channel (preferred) or create a GitHub issue to discuss it first. This will help ensure your work aligns with the project's goals and avoid duplication of effort. ## Prerequisites diff --git a/TODO.md b/TODO.md index c5a5e2411..7c7a767cd 100644 --- a/TODO.md +++ b/TODO.md @@ -77,7 +77,6 @@ - [x] JSDoc for CRUD methods - [x] Cache validation schemas - [x] Compound ID - - [ ] Cross field comparison - [x] Many-to-many relation - [x] Self relation - [ ] Empty AND/OR/NOT behavior @@ -101,6 +100,7 @@ - [x] Validation - [ ] Access Policy - [ ] Short-circuit pre-create check for scalar-field only policies + - [x] Field-level policies - [x] Inject "on conflict do update" - [x] `check` function - [ ] Custom functions diff --git a/package.json b/package.json index bbe74ef70..08796a1d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-v3", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack", "packageManager": "pnpm@10.23.0", "type": "module", diff --git a/packages/auth-adapters/better-auth/package.json b/packages/auth-adapters/better-auth/package.json index 56d80db91..7be7725e2 100644 --- a/packages/auth-adapters/better-auth/package.json +++ b/packages/auth-adapters/better-auth/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/better-auth", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.", "type": "module", "scripts": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 2c86744ba..c70afd24b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack CLI", "description": "FullStack database toolkit with built-in access control and automatic API generation.", - "version": "3.1.1", + "version": "3.2.0", "type": "module", "author": { "name": "ZenStack Team" @@ -21,6 +21,10 @@ "zen": "bin/cli", "zenstack": "bin/cli" }, + "files": [ + "dist", + "bin" + ], "scripts": { "build": "tsc --noEmit && tsup-node", "watch": "tsup-node --watch", @@ -35,6 +39,7 @@ "@zenstackhq/common-helpers": "workspace:*", "@zenstackhq/language": "workspace:*", "@zenstackhq/sdk": "workspace:*", + "chokidar": "^5.0.0", "colors": "1.4.0", "commander": "^8.3.0", "execa": "^9.6.0", @@ -58,5 +63,8 @@ "@zenstackhq/vitest-config": "workspace:*", "better-sqlite3": "catalog:", "tmp": "catalog:" + }, + "engines": { + "node": ">=20" } } diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index c41a99ea4..16e3826c7 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -1,5 +1,6 @@ -import { invariant } from '@zenstackhq/common-helpers'; -import { isPlugin, LiteralExpr, Plugin, type Model } from '@zenstackhq/language/ast'; +import { invariant, singleDebounce } from '@zenstackhq/common-helpers'; +import { ZModelLanguageMetaData } from '@zenstackhq/language'; +import { type AbstractDeclaration, isPlugin, LiteralExpr, Plugin, type Model } from '@zenstackhq/language/ast'; import { getLiteral, getLiteralArray } from '@zenstackhq/language/utils'; import { type CliPlugin } from '@zenstackhq/sdk'; import colors from 'colors'; @@ -7,6 +8,7 @@ import { createJiti } from 'jiti'; import fs from 'node:fs'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; +import { watch } from 'chokidar'; import ora, { type Ora } from 'ora'; import { CliError } from '../cli-error'; import * as corePlugins from '../plugins'; @@ -16,6 +18,7 @@ type Options = { schema?: string; output?: string; silent: boolean; + watch: boolean; lite: boolean; liteOnly: boolean; }; @@ -24,6 +27,96 @@ type Options = { * CLI action for generating code from schema */ export async function run(options: Options) { + const model = await pureGenerate(options, false); + + if (options.watch) { + const logsEnabled = !options.silent; + + if (logsEnabled) { + console.log(colors.green(`\nEnabled watch mode!`)); + } + + const schemaExtensions = ZModelLanguageMetaData.fileExtensions; + + // Get real models file path (cuz its merged into single document -> we need use cst nodes) + const getRootModelWatchPaths = (model: Model) => new Set( + ( + model.declarations.filter( + (v) => + v.$cstNode?.parent?.element.$type === 'Model' && + !!v.$cstNode.parent.element.$document?.uri?.fsPath, + ) as AbstractDeclaration[] + ).map((v) => v.$cstNode!.parent!.element.$document!.uri!.fsPath), + ); + + const watchedPaths = getRootModelWatchPaths(model); + + if (logsEnabled) { + const logPaths = [...watchedPaths].map((at) => `- ${at}`).join('\n'); + console.log(`Watched file paths:\n${logPaths}`); + } + + const watcher = watch([...watchedPaths], { + alwaysStat: false, + ignoreInitial: true, + ignorePermissionErrors: true, + ignored: (at) => !schemaExtensions.some((ext) => at.endsWith(ext)), + }); + + // prevent save multiple files and run multiple times + const reGenerateSchema = singleDebounce(async () => { + if (logsEnabled) { + console.log('Got changes, run generation!'); + } + + try { + const newModel = await pureGenerate(options, true); + const allModelsPaths = getRootModelWatchPaths(newModel); + const newModelPaths = [...allModelsPaths].filter((at) => !watchedPaths.has(at)); + const removeModelPaths = [...watchedPaths].filter((at) => !allModelsPaths.has(at)); + + if (newModelPaths.length) { + if (logsEnabled) { + const logPaths = newModelPaths.map((at) => `- ${at}`).join('\n'); + console.log(`Added file(s) to watch:\n${logPaths}`); + } + + newModelPaths.forEach((at) => watchedPaths.add(at)); + watcher.add(newModelPaths); + } + + if (removeModelPaths.length) { + if (logsEnabled) { + const logPaths = removeModelPaths.map((at) => `- ${at}`).join('\n'); + console.log(`Removed file(s) from watch:\n${logPaths}`); + } + + removeModelPaths.forEach((at) => watchedPaths.delete(at)); + watcher.unwatch(removeModelPaths); + } + } catch (e) { + console.error(e); + } + }, 500, true); + + watcher.on('unlink', (pathAt) => { + if (logsEnabled) { + console.log(`Removed file from watch: ${pathAt}`); + } + + watchedPaths.delete(pathAt); + watcher.unwatch(pathAt); + + reGenerateSchema(); + }); + + watcher.on('change', () => { + reGenerateSchema(); + }); + } +} + +async function pureGenerate(options: Options, fromWatch: boolean) { const start = Date.now(); const schemaFile = getSchemaFile(options.schema); @@ -35,7 +128,9 @@ export async function run(options: Options) { if (!options.silent) { console.log(colors.green(`Generation completed successfully in ${Date.now() - start}ms.\n`)); - console.log(`You can now create a ZenStack client with it. + + if (!fromWatch) { + console.log(`You can now create a ZenStack client with it. \`\`\`ts import { ZenStackClient } from '@zenstackhq/orm'; @@ -47,7 +142,10 @@ const client = new ZenStackClient(schema, { \`\`\` Check documentation: https://zenstack.dev/docs/`); + } } + + return model; } function getOutputPath(options: Options, schemaFile: string) { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c2307fa1d..0d663044c 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -68,6 +68,7 @@ function createProgram() { .addOption(schemaOption) .addOption(noVersionCheckOption) .addOption(new Option('-o, --output ', 'default output directory for code generation')) + .addOption(new Option('-w, --watch', 'enable watch mode').default(false)) .addOption(new Option('--lite', 'also generate a lite version of schema without attributes').default(false)) .addOption(new Option('--lite-only', 'only generate lite version of schema without attributes').default(false)) .addOption(new Option('--silent', 'suppress all output except errors').default(false)) @@ -220,6 +221,11 @@ async function main() { } } + if (program.args.includes('generate') && (program.args.includes('-w') || program.args.includes('--watch'))) { + // A "hack" way to prevent the process from terminating because we don't want to stop it. + return; + } + if (telemetry.isTracking) { // give telemetry a chance to send events before exit setTimeout(() => { diff --git a/packages/cli/test/ts-schema-gen.test.ts b/packages/cli/test/ts-schema-gen.test.ts index 2673567d0..1056478ff 100644 --- a/packages/cli/test/ts-schema-gen.test.ts +++ b/packages/cli/test/ts-schema-gen.test.ts @@ -184,6 +184,30 @@ model Post { }); }); + it('generates correct procedures with array params and returns', async () => { + const { schema } = await generateTsSchema(` +model User { + id Int @id +} + +procedure findByIds(ids: Int[]): User[] +procedure getIds(): Int[] + `); + + expect(schema.procedures).toMatchObject({ + findByIds: { + params: { ids: { name: 'ids', type: 'Int', array: true } }, + returnType: 'User', + returnArray: true, + }, + getIds: { + params: {}, + returnType: 'Int', + returnArray: true, + }, + }); + }); + it('merges fields and attributes from mixins', async () => { const { schema } = await generateTsSchema(` type Timestamped { diff --git a/packages/clients/client-helpers/package.json b/packages/clients/client-helpers/package.json index df184bc57..d7a4bd9b8 100644 --- a/packages/clients/client-helpers/package.json +++ b/packages/clients/client-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/client-helpers", - "version": "3.1.1", + "version": "3.2.0", "description": "Helpers for implementing clients that consume ZenStack's CRUD service", "type": "module", "scripts": { @@ -13,6 +13,9 @@ }, "author": "ZenStack Team", "license": "MIT", + "files": [ + "dist" + ], "exports": { ".": { "types": "./dist/index.d.ts", diff --git a/packages/clients/tanstack-query/package.json b/packages/clients/tanstack-query/package.json index 49a813b10..35b80f978 100644 --- a/packages/clients/tanstack-query/package.json +++ b/packages/clients/tanstack-query/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/tanstack-query", - "version": "3.1.1", + "version": "3.2.0", "description": "TanStack Query Client for consuming ZenStack v3's CRUD service", "type": "module", "scripts": { diff --git a/packages/clients/tanstack-query/src/common/constants.ts b/packages/clients/tanstack-query/src/common/constants.ts new file mode 100644 index 000000000..15684479d --- /dev/null +++ b/packages/clients/tanstack-query/src/common/constants.ts @@ -0,0 +1 @@ +export const CUSTOM_PROC_ROUTE_NAME = '$procs'; diff --git a/packages/clients/tanstack-query/src/common/types.ts b/packages/clients/tanstack-query/src/common/types.ts index 8993445ed..2e61d286d 100644 --- a/packages/clients/tanstack-query/src/common/types.ts +++ b/packages/clients/tanstack-query/src/common/types.ts @@ -1,6 +1,6 @@ import type { Logger, OptimisticDataProvider } from '@zenstackhq/client-helpers'; import type { FetchFn } from '@zenstackhq/client-helpers/fetch'; -import type { OperationsIneligibleForDelegateModels } from '@zenstackhq/orm'; +import type { GetProcedureNames, OperationsIneligibleForDelegateModels, ProcedureFunc } from '@zenstackhq/orm'; import type { GetModels, IsDelegateModel, SchemaDef } from '@zenstackhq/schema'; /** @@ -76,3 +76,7 @@ type WithOptimisticFlag = T extends object : T; export type WithOptimistic = T extends Array ? Array> : WithOptimisticFlag; + +export type ProcedureReturn> = Awaited< + ReturnType> +>; diff --git a/packages/clients/tanstack-query/src/react.ts b/packages/clients/tanstack-query/src/react.ts index 4449e29fb..75045f5cd 100644 --- a/packages/clients/tanstack-query/src/react.ts +++ b/packages/clients/tanstack-query/src/react.ts @@ -33,11 +33,15 @@ import type { CreateManyArgs, DeleteArgs, DeleteManyArgs, + ExistsArgs, FindFirstArgs, FindManyArgs, FindUniqueArgs, + GetProcedure, + GetProcedureNames, GroupByArgs, GroupByResult, + ProcedureEnvelope, QueryOptions, SelectSubset, SimplifiedPlainResult, @@ -51,16 +55,28 @@ import type { import type { GetModels, SchemaDef } from '@zenstackhq/schema'; import { createContext, useContext } from 'react'; import { getAllQueries, invalidateQueriesMatchingPredicate } from './common/client'; +import { CUSTOM_PROC_ROUTE_NAME } from './common/constants'; import { getQueryKey } from './common/query-key'; import type { ExtraMutationOptions, ExtraQueryOptions, + ProcedureReturn, QueryContext, TrimDelegateModelOperations, WithOptimistic, } from './common/types'; export type { FetchFn } from '@zenstackhq/client-helpers/fetch'; +type ProcedureHookFn< + Schema extends SchemaDef, + ProcName extends GetProcedureNames, + Options, + Result, + Input = ProcedureEnvelope, +> = { args: undefined } extends Input + ? (input?: Input, options?: Options) => Result + : (input: Input, options?: Options) => Result; + /** * React context for query settings. */ @@ -133,8 +149,61 @@ export type ModelMutationModelResult< export type ClientHooks = QueryOptions> = { [Model in GetModels as `${Uncapitalize}`]: ModelQueryHooks; +} & ProcedureHooks; + +type ProcedureHookGroup = { + [Name in GetProcedureNames]: GetProcedure extends { mutation: true } + ? { + useMutation( + options?: Omit< + UseMutationOptions, DefaultError, ProcedureEnvelope>, + 'mutationFn' + > & + QueryContext, + ): UseMutationResult, DefaultError, ProcedureEnvelope>; + } + : { + useQuery: ProcedureHookFn< + Schema, + Name, + Omit>, 'optimisticUpdate'>, + UseQueryResult, DefaultError> & { queryKey: QueryKey } + >; + + useSuspenseQuery: ProcedureHookFn< + Schema, + Name, + Omit>, 'optimisticUpdate'>, + UseSuspenseQueryResult, DefaultError> & { queryKey: QueryKey } + >; + + // Infinite queries for procedures are currently disabled, will add back later if needed + // + // useInfiniteQuery: ProcedureHookFn< + // Schema, + // Name, + // ModelInfiniteQueryOptions>, + // ModelInfiniteQueryResult>> + // >; + + // useSuspenseInfiniteQuery: ProcedureHookFn< + // Schema, + // Name, + // ModelSuspenseInfiniteQueryOptions>, + // ModelSuspenseInfiniteQueryResult>> + // >; + }; }; +export type ProcedureHooks = Schema extends { procedures: Record } + ? { + /** + * Custom procedures. + */ + $procs: ProcedureHookGroup; + } + : {}; + // Note that we can potentially use TypeScript's mapped type to directly map from ORM contract, but that seems // to significantly slow down tsc performance ... export type ModelQueryHooks< @@ -165,6 +234,11 @@ export type ModelQueryHooks< options?: ModelSuspenseQueryOptions | null>, ): ModelSuspenseQueryResult | null>; + useExists>( + args?: Subset>, + options?: ModelQueryOptions, + ): ModelQueryResult; + useFindMany>( args?: SelectSubset>, options?: ModelQueryOptions[]>, @@ -263,7 +337,7 @@ export function useClientQueries { - return Object.keys(schema.models).reduce( + const result = Object.keys(schema.models).reduce( (acc, model) => { (acc as any)[lowerCaseFirst(model)] = useModelQueries, Options>( schema, @@ -274,6 +348,52 @@ export function useClientQueries, ); + + const procedures = (schema as any).procedures as Record | undefined; + if (procedures) { + const buildProcedureHooks = () => { + return Object.keys(procedures).reduce((acc, name) => { + const procDef = procedures[name]; + if (procDef?.mutation) { + acc[name] = { + useMutation: (hookOptions?: any) => + useInternalMutation(schema, CUSTOM_PROC_ROUTE_NAME, 'POST', name, { + ...options, + ...hookOptions, + }), + }; + } else { + acc[name] = { + useQuery: (args?: any, hookOptions?: any) => + useInternalQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, { + ...options, + ...hookOptions, + }), + useSuspenseQuery: (args?: any, hookOptions?: any) => + useInternalSuspenseQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, { + ...options, + ...hookOptions, + }), + useInfiniteQuery: (args?: any, hookOptions?: any) => + useInternalInfiniteQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, { + ...options, + ...hookOptions, + }), + useSuspenseInfiniteQuery: (args?: any, hookOptions?: any) => + useInternalSuspenseInfiniteQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, { + ...options, + ...hookOptions, + }), + }; + } + return acc; + }, {} as any); + }; + + (result as any).$procs = buildProcedureHooks(); + } + + return result; } /** @@ -308,6 +428,10 @@ export function useModelQueries< return useInternalSuspenseQuery(schema, modelName, 'findFirst', args, { ...rootOptions, ...options }); }, + useExists: (args: any, options?: any) => { + return useInternalQuery(schema, modelName, 'exists', args, { ...rootOptions, ...options }); + }, + useFindMany: (args: any, options?: any) => { return useInternalQuery(schema, modelName, 'findMany', args, { ...rootOptions, ...options }); }, @@ -528,64 +652,68 @@ export function useInternalMutation( }; const finalOptions = { ...options, mutationFn }; - const invalidateQueries = options?.invalidateQueries !== false; - const optimisticUpdate = !!options?.optimisticUpdate; - - if (!optimisticUpdate) { - // if optimistic update is not enabled, invalidate related queries on success - if (invalidateQueries) { - const invalidator = createInvalidator( + if (model !== CUSTOM_PROC_ROUTE_NAME) { + // not a custom procedure, set up optimistic update and invalidation + + const invalidateQueries = options?.invalidateQueries !== false; + const optimisticUpdate = !!options?.optimisticUpdate; + + if (!optimisticUpdate) { + // if optimistic update is not enabled, invalidate related queries on success + if (invalidateQueries) { + const invalidator = createInvalidator( + model, + operation, + schema, + (predicate) => invalidateQueriesMatchingPredicate(queryClient, predicate), + logging, + ); + const origOnSuccess = finalOptions.onSuccess; + finalOptions.onSuccess = async (...args) => { + // execute invalidator prior to user-provided onSuccess + await invalidator(...args); + + // call user-provided onSuccess + await origOnSuccess?.(...args); + }; + } + } else { + // schedule optimistic update on mutate + const optimisticUpdater = createOptimisticUpdater( model, operation, schema, - (predicate) => invalidateQueriesMatchingPredicate(queryClient, predicate), + { optimisticDataProvider: finalOptions.optimisticDataProvider }, + () => getAllQueries(queryClient), logging, ); - const origOnSuccess = finalOptions.onSuccess; - finalOptions.onSuccess = async (...args) => { - // execute invalidator prior to user-provided onSuccess - await invalidator(...args); + const origOnMutate = finalOptions.onMutate; + finalOptions.onMutate = async (...args) => { + // execute optimistic update + await optimisticUpdater(...args); - // call user-provided onSuccess - await origOnSuccess?.(...args); + // call user-provided onMutate + return origOnMutate?.(...args); }; - } - } else { - // schedule optimistic update on mutate - const optimisticUpdater = createOptimisticUpdater( - model, - operation, - schema, - { optimisticDataProvider: finalOptions.optimisticDataProvider }, - () => getAllQueries(queryClient), - logging, - ); - const origOnMutate = finalOptions.onMutate; - finalOptions.onMutate = async (...args) => { - // execute optimistic update - await optimisticUpdater(...args); - - // call user-provided onMutate - return origOnMutate?.(...args); - }; - - if (invalidateQueries) { - // invalidate related queries on settled (success or error) - const invalidator = createInvalidator( - model, - operation, - schema, - (predicate) => invalidateQueriesMatchingPredicate(queryClient, predicate), - logging, - ); - const origOnSettled = finalOptions.onSettled; - finalOptions.onSettled = async (...args) => { - // execute invalidator prior to user-provided onSettled - await invalidator(...args); - // call user-provided onSettled - return origOnSettled?.(...args); - }; + if (invalidateQueries) { + // invalidate related queries on settled (success or error) + const invalidator = createInvalidator( + model, + operation, + schema, + (predicate) => invalidateQueriesMatchingPredicate(queryClient, predicate), + logging, + ); + const origOnSettled = finalOptions.onSettled; + finalOptions.onSettled = async (...args) => { + // execute invalidator prior to user-provided onSettled + await invalidator(...args); + + // call user-provided onSettled + return origOnSettled?.(...args); + }; + } } } diff --git a/packages/clients/tanstack-query/src/svelte/index.svelte.ts b/packages/clients/tanstack-query/src/svelte/index.svelte.ts index a94941c40..0c976b715 100644 --- a/packages/clients/tanstack-query/src/svelte/index.svelte.ts +++ b/packages/clients/tanstack-query/src/svelte/index.svelte.ts @@ -34,11 +34,15 @@ import type { CreateManyArgs, DeleteArgs, DeleteManyArgs, + ExistsArgs, FindFirstArgs, FindManyArgs, FindUniqueArgs, + GetProcedure, + GetProcedureNames, GroupByArgs, GroupByResult, + ProcedureEnvelope, QueryOptions, SelectSubset, SimplifiedPlainResult, @@ -52,16 +56,28 @@ import type { import type { GetModels, SchemaDef } from '@zenstackhq/schema'; import { getContext, setContext } from 'svelte'; import { getAllQueries, invalidateQueriesMatchingPredicate } from '../common/client'; +import { CUSTOM_PROC_ROUTE_NAME } from '../common/constants'; import { getQueryKey } from '../common/query-key'; import type { ExtraMutationOptions, ExtraQueryOptions, + ProcedureReturn, QueryContext, TrimDelegateModelOperations, WithOptimistic, } from '../common/types'; export type { FetchFn } from '@zenstackhq/client-helpers/fetch'; +type ProcedureHookFn< + Schema extends SchemaDef, + ProcName extends GetProcedureNames, + Options, + Result, + Input = ProcedureEnvelope, +> = { args: undefined } extends Input + ? (args?: Accessor, options?: Accessor) => Result + : (args: Accessor, options?: Accessor) => Result; + /** * Key for setting and getting the global query context. */ @@ -88,6 +104,14 @@ function useQuerySettings() { return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest }; } +function merge(rootOpt: unknown, opt: unknown): Accessor { + return () => { + const rootOptVal = typeof rootOpt === 'function' ? (rootOpt as any)() : rootOpt; + const optVal = typeof opt === 'function' ? (opt as any)() : opt; + return { ...rootOptVal, ...optVal }; + }; +} + export type ModelQueryOptions = Omit, 'queryKey'> & ExtraQueryOptions; export type ModelQueryResult = CreateQueryResult, DefaultError> & { queryKey: QueryKey }; @@ -122,8 +146,51 @@ export type ModelMutationModelResult< export type ClientHooks = QueryOptions> = { [Model in GetModels as `${Uncapitalize}`]: ModelQueryHooks; +} & ProcedureHooks; + +type ProcedureHookGroup = { + [Name in GetProcedureNames]: GetProcedure extends { mutation: true } + ? { + useMutation( + options?: Omit< + CreateMutationOptions< + ProcedureReturn, + DefaultError, + ProcedureEnvelope + >, + 'mutationFn' + > & + QueryContext, + ): CreateMutationResult, DefaultError, ProcedureEnvelope>; + } + : { + useQuery: ProcedureHookFn< + Schema, + Name, + Omit>, 'optimisticUpdate'>, + CreateQueryResult, DefaultError> & { queryKey: QueryKey } + >; + + // Infinite queries for procedures are currently disabled, will add back later if needed + // + // useInfiniteQuery: ProcedureHookFn< + // Schema, + // Name, + // ModelInfiniteQueryOptions>, + // ModelInfiniteQueryResult>> + // >; + }; }; +export type ProcedureHooks = Schema extends { procedures: Record } + ? { + /** + * Custom procedures. + */ + $procs: ProcedureHookGroup; + } + : {}; + // Note that we can potentially use TypeScript's mapped type to directly map from ORM contract, but that seems // to significantly slow down tsc performance ... export type ModelQueryHooks< @@ -144,6 +211,11 @@ export type ModelQueryHooks< options?: Accessor | null>>, ): ModelQueryResult | null>; + useExists>( + args?: Accessor>>, + options?: Accessor>, + ): ModelQueryResult; + useFindMany>( args?: Accessor>>, options?: Accessor[]>>, @@ -212,7 +284,7 @@ export function useClientQueries, ): ClientHooks { - return Object.keys(schema.models).reduce( + const result = Object.keys(schema.models).reduce( (acc, model) => { (acc as any)[lowerCaseFirst(model)] = useModelQueries, Options>( schema, @@ -223,6 +295,45 @@ export function useClientQueries, ); + + const procedures = (schema as any).procedures as Record | undefined; + if (procedures) { + const buildProcedureHooks = () => { + return Object.keys(procedures).reduce((acc, name) => { + const procDef = procedures[name]; + if (procDef?.mutation) { + acc[name] = { + useMutation: (hookOptions?: any) => + useInternalMutation( + schema, + CUSTOM_PROC_ROUTE_NAME, + 'POST', + name, + merge(options, hookOptions), + ), + }; + } else { + acc[name] = { + useQuery: (args?: any, hookOptions?: any) => + useInternalQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, merge(options, hookOptions)), + useInfiniteQuery: (args?: any, hookOptions?: any) => + useInternalInfiniteQuery( + schema, + CUSTOM_PROC_ROUTE_NAME, + name, + args, + merge(options, hookOptions), + ), + }; + } + return acc; + }, {} as any); + }; + + (result as any).$procs = buildProcedureHooks(); + } + + return result; } /** @@ -240,14 +351,6 @@ export function useModelQueries< const modelName = modelDef.name; - const merge = (rootOpt: unknown, opt: unknown): Accessor => { - return () => { - const rootOptVal = typeof rootOpt === 'function' ? rootOpt() : rootOpt; - const optVal = typeof opt === 'function' ? opt() : opt; - return { ...rootOptVal, ...optVal }; - }; - }; - return { useFindUnique: (args: any, options?: any) => { return useInternalQuery(schema, modelName, 'findUnique', args, merge(rootOptions, options)); @@ -257,6 +360,10 @@ export function useModelQueries< return useInternalQuery(schema, modelName, 'findFirst', args, merge(rootOptions, options)); }, + useExists: (args: any, options?: any) => { + return useInternalQuery(schema, modelName, 'exists', args, merge(rootOptions, options)); + }, + useFindMany: (args: any, options?: any) => { return useInternalQuery(schema, modelName, 'findMany', args, merge(rootOptions, options)); }, @@ -439,70 +546,74 @@ export function useInternalMutation( mutationFn, }; - if (!optimisticUpdate) { - // if optimistic update is not enabled, invalidate related queries on success - if (invalidateQueries) { - const invalidator = createInvalidator( + if (model !== CUSTOM_PROC_ROUTE_NAME) { + // not a custom procedure, set up optimistic update and invalidation + + if (!optimisticUpdate) { + // if optimistic update is not enabled, invalidate related queries on success + if (invalidateQueries) { + const invalidator = createInvalidator( + model, + operation, + schema, + (predicate: InvalidationPredicate) => + // @ts-ignore + invalidateQueriesMatchingPredicate(queryClient, predicate), + logging, + ); + + // execute invalidator prior to user-provided onSuccess + const origOnSuccess = optionsValue?.onSuccess; + const wrappedOnSuccess: typeof origOnSuccess = async (...args) => { + await invalidator(...args); + await origOnSuccess?.(...args); + }; + result.onSuccess = wrappedOnSuccess; + } + } else { + const optimisticUpdater = createOptimisticUpdater( model, operation, schema, - (predicate: InvalidationPredicate) => - // @ts-ignore - invalidateQueriesMatchingPredicate(queryClient, predicate), + { optimisticDataProvider: optionsValue?.optimisticDataProvider }, + // @ts-ignore + () => getAllQueries(queryClient), logging, ); - // execute invalidator prior to user-provided onSuccess - const origOnSuccess = optionsValue?.onSuccess; - const wrappedOnSuccess: typeof origOnSuccess = async (...args) => { - await invalidator(...args); - await origOnSuccess?.(...args); - }; - result.onSuccess = wrappedOnSuccess; - } - } else { - const optimisticUpdater = createOptimisticUpdater( - model, - operation, - schema, - { optimisticDataProvider: optionsValue?.optimisticDataProvider }, - // @ts-ignore - () => getAllQueries(queryClient), - logging, - ); - - const origOnMutate = optionsValue.onMutate; - const wrappedOnMutate: typeof origOnMutate = async (...args) => { - // execute optimistic updater prior to user-provided onMutate - await optimisticUpdater(...args); - - // call user-provided onMutate - return origOnMutate?.(...args); - }; - - result.onMutate = wrappedOnMutate; - - if (invalidateQueries) { - const invalidator = createInvalidator( - model, - operation, - schema, - (predicate: InvalidationPredicate) => - // @ts-ignore - invalidateQueriesMatchingPredicate(queryClient, predicate), - logging, - ); - const origOnSettled = optionsValue.onSettled; - const wrappedOnSettled: typeof origOnSettled = async (...args) => { - // execute invalidator prior to user-provided onSettled - await invalidator(...args); + const origOnMutate = optionsValue.onMutate; + const wrappedOnMutate: typeof origOnMutate = async (...args) => { + // execute optimistic updater prior to user-provided onMutate + await optimisticUpdater(...args); - // call user-provided onSettled - await origOnSettled?.(...args); + // call user-provided onMutate + return origOnMutate?.(...args); }; - // replace onSettled in mergedOpt - result.onSettled = wrappedOnSettled; + result.onMutate = wrappedOnMutate; + + if (invalidateQueries) { + const invalidator = createInvalidator( + model, + operation, + schema, + (predicate: InvalidationPredicate) => + // @ts-ignore + invalidateQueriesMatchingPredicate(queryClient, predicate), + logging, + ); + const origOnSettled = optionsValue.onSettled; + const wrappedOnSettled: typeof origOnSettled = async (...args) => { + // execute invalidator prior to user-provided onSettled + await invalidator(...args); + + // call user-provided onSettled + await origOnSettled?.(...args); + }; + + // replace onSettled in mergedOpt + result.onSettled = wrappedOnSettled; + } } } diff --git a/packages/clients/tanstack-query/src/vue.ts b/packages/clients/tanstack-query/src/vue.ts index bd4dcf74b..bc0bf39f3 100644 --- a/packages/clients/tanstack-query/src/vue.ts +++ b/packages/clients/tanstack-query/src/vue.ts @@ -32,11 +32,15 @@ import type { CreateManyArgs, DeleteArgs, DeleteManyArgs, + ExistsArgs, FindFirstArgs, FindManyArgs, FindUniqueArgs, + GetProcedure, + GetProcedureNames, GroupByArgs, GroupByResult, + ProcedureEnvelope, QueryOptions, SelectSubset, SimplifiedPlainResult, @@ -50,10 +54,12 @@ import type { import type { GetModels, SchemaDef } from '@zenstackhq/schema'; import { computed, inject, provide, toValue, unref, type MaybeRefOrGetter, type Ref, type UnwrapRef } from 'vue'; import { getAllQueries, invalidateQueriesMatchingPredicate } from './common/client'; +import { CUSTOM_PROC_ROUTE_NAME } from './common/constants'; import { getQueryKey } from './common/query-key'; import type { ExtraMutationOptions, ExtraQueryOptions, + ProcedureReturn, QueryContext, TrimDelegateModelOperations, WithOptimistic, @@ -61,6 +67,16 @@ import type { export type { FetchFn } from '@zenstackhq/client-helpers/fetch'; export const VueQueryContextKey = 'zenstack-vue-query-context'; +type ProcedureHookFn< + Schema extends SchemaDef, + ProcName extends GetProcedureNames, + Options, + Result, + Input = ProcedureEnvelope, +> = { args: undefined } extends Input + ? (args?: MaybeRefOrGetter, options?: MaybeRefOrGetter) => Result + : (args: MaybeRefOrGetter, options?: MaybeRefOrGetter) => Result; + /** * Provide context for query settings. * @@ -123,8 +139,60 @@ export type ModelMutationModelResult< export type ClientHooks = QueryOptions> = { [Model in GetModels as `${Uncapitalize}`]: ModelQueryHooks; +} & ProcedureHooks; + +type ProcedureHookGroup = { + [Name in GetProcedureNames]: GetProcedure extends { mutation: true } + ? { + useMutation( + options?: MaybeRefOrGetter< + Omit< + UnwrapRef< + UseMutationOptions< + ProcedureReturn, + DefaultError, + ProcedureEnvelope + > + >, + 'mutationFn' + > & + QueryContext + >, + ): UseMutationReturnType< + ProcedureReturn, + DefaultError, + ProcedureEnvelope, + unknown + >; + } + : { + useQuery: ProcedureHookFn< + Schema, + Name, + Omit>, 'optimisticUpdate'>, + UseQueryReturnType, DefaultError> & { queryKey: Ref } + >; + + // Infinite queries for procedures are currently disabled, will add back later if needed + // + // useInfiniteQuery: ProcedureHookFn< + // Schema, + // Name, + // ModelInfiniteQueryOptions>, + // ModelInfiniteQueryResult>> + // >; + }; }; +export type ProcedureHooks = Schema extends { procedures: Record } + ? { + /** + * Custom procedures. + */ + $procs: ProcedureHookGroup; + } + : {}; + // Note that we can potentially use TypeScript's mapped type to directly map from ORM contract, but that seems // to significantly slow down tsc performance ... export type ModelQueryHooks< @@ -145,6 +213,11 @@ export type ModelQueryHooks< options?: MaybeRefOrGetter | null>>, ): ModelQueryResult | null>; + useExists>( + args?: MaybeRefOrGetter>>, + options?: MaybeRefOrGetter>, + ): ModelQueryResult; + useFindMany>( args?: MaybeRefOrGetter>>, options?: MaybeRefOrGetter[]>>, @@ -215,7 +288,15 @@ export function useClientQueries, ): ClientHooks { - return Object.keys(schema.models).reduce( + const merge = (rootOpt: MaybeRefOrGetter | undefined, opt: MaybeRefOrGetter | undefined): any => { + return computed(() => { + const rootVal = toValue(rootOpt) ?? {}; + const optVal = toValue(opt) ?? {}; + return { ...(rootVal as object), ...(optVal as object) }; + }); + }; + + const result = Object.keys(schema.models).reduce( (acc, model) => { (acc as any)[lowerCaseFirst(model)] = useModelQueries, Options>( schema, @@ -226,6 +307,45 @@ export function useClientQueries, ); + + const procedures = (schema as any).procedures as Record | undefined; + if (procedures) { + const buildProcedureHooks = () => { + return Object.keys(procedures).reduce((acc, name) => { + const procDef = procedures[name]; + if (procDef?.mutation) { + acc[name] = { + useMutation: (hookOptions?: any) => + useInternalMutation( + schema, + CUSTOM_PROC_ROUTE_NAME, + 'POST', + name, + merge(options, hookOptions), + ), + }; + } else { + acc[name] = { + useQuery: (args?: any, hookOptions?: any) => + useInternalQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, merge(options, hookOptions)), + useInfiniteQuery: (args?: any, hookOptions?: any) => + useInternalInfiniteQuery( + schema, + CUSTOM_PROC_ROUTE_NAME, + name, + args, + merge(options, hookOptions), + ), + }; + } + return acc; + }, {} as any); + }; + + (result as any).$procs = buildProcedureHooks(); + } + + return result; } /** @@ -258,6 +378,10 @@ export function useModelQueries< return useInternalQuery(schema, modelName, 'findFirst', args, merge(rootOptions, options)); }, + useExists: (args: any, options?: any) => { + return useInternalQuery(schema, modelName, 'exists', args, merge(rootOptions, options)); + }, + useFindMany: (args: any, options?: any) => { return useInternalQuery(schema, modelName, 'findMany', args, merge(rootOptions, options)); }, @@ -441,63 +565,70 @@ export function useInternalMutation( mutationFn, } as UnwrapRef> & ExtraMutationOptions; - const invalidateQueries = optionsValue?.invalidateQueries !== false; - const optimisticUpdate = !!optionsValue?.optimisticUpdate; - - if (!optimisticUpdate) { - if (invalidateQueries) { - const invalidator = createInvalidator( + if (model !== CUSTOM_PROC_ROUTE_NAME) { + // not a custom procedure, set up optimistic update and invalidation + + const invalidateQueries = optionsValue?.invalidateQueries !== false; + const optimisticUpdate = !!optionsValue?.optimisticUpdate; + + if (!optimisticUpdate) { + if (invalidateQueries) { + const invalidator = createInvalidator( + model, + operation, + schema, + (predicate: InvalidationPredicate) => + invalidateQueriesMatchingPredicate(queryClient, predicate), + logging, + ); + // execute invalidator prior to user-provided onSuccess + result.onSuccess = async (...args) => { + await invalidator(...args); + const origOnSuccess: any = toValue(optionsValue?.onSuccess); + await origOnSuccess?.(...args); + }; + } + } else { + const optimisticUpdater = createOptimisticUpdater( model, operation, schema, - (predicate: InvalidationPredicate) => invalidateQueriesMatchingPredicate(queryClient, predicate), + { optimisticDataProvider: result.optimisticDataProvider }, + () => getAllQueries(queryClient), logging, ); - // execute invalidator prior to user-provided onSuccess - result.onSuccess = async (...args) => { - await invalidator(...args); - const origOnSuccess: any = toValue(optionsValue?.onSuccess); - await origOnSuccess?.(...args); - }; - } - } else { - const optimisticUpdater = createOptimisticUpdater( - model, - operation, - schema, - { optimisticDataProvider: result.optimisticDataProvider }, - () => getAllQueries(queryClient), - logging, - ); - // optimistic update on mutate - const origOnMutate = result.onMutate; - result.onMutate = async (...args) => { - // execute optimistic updater prior to user-provided onMutate - await optimisticUpdater(...args); + // optimistic update on mutate + const origOnMutate = result.onMutate; + result.onMutate = async (...args) => { + // execute optimistic updater prior to user-provided onMutate + await optimisticUpdater(...args); - // call user-provided onMutate - return unref(origOnMutate)?.(...args); - }; - - if (invalidateQueries) { - const invalidator = createInvalidator( - model, - operation, - schema, - (predicate: InvalidationPredicate) => invalidateQueriesMatchingPredicate(queryClient, predicate), - logging, - ); - const origOnSettled = result.onSettled; - result.onSettled = async (...args) => { - // execute invalidator prior to user-provided onSettled - await invalidator(...args); - - // call user-provided onSettled - return unref(origOnSettled)?.(...args); + // call user-provided onMutate + return unref(origOnMutate)?.(...args); }; + + if (invalidateQueries) { + const invalidator = createInvalidator( + model, + operation, + schema, + (predicate: InvalidationPredicate) => + invalidateQueriesMatchingPredicate(queryClient, predicate), + logging, + ); + const origOnSettled = result.onSettled; + result.onSettled = async (...args) => { + // execute invalidator prior to user-provided onSettled + await invalidator(...args); + + // call user-provided onSettled + return unref(origOnSettled)?.(...args); + }; + } } } + return result; }); diff --git a/packages/clients/tanstack-query/test/react-typing-test.ts b/packages/clients/tanstack-query/test/react-typing-test.ts index 8f57ec670..b99a31217 100644 --- a/packages/clients/tanstack-query/test/react-typing-test.ts +++ b/packages/clients/tanstack-query/test/react-typing-test.ts @@ -1,7 +1,9 @@ import { useClientQueries } from '../src/react'; import { schema } from './schemas/basic/schema-lite'; +import { schema as proceduresSchema } from './schemas/procedures/schema-lite'; const client = useClientQueries(schema); +const proceduresClient = useClientQueries(proceduresSchema); // @ts-expect-error missing args client.user.useFindUnique(); @@ -18,6 +20,9 @@ check(client.user.useFindUnique({ where: { id: '1' }, include: { posts: true } } check(client.user.useFindFirst().data?.email); check(client.user.useFindFirst().data?.$optimistic); +check(client.user.useExists().data); +check(client.user.useExists({ where: { id: '1' } }).data); + check(client.user.useFindMany().data?.[0]?.email); check(client.user.useFindMany().data?.[0]?.$optimistic); @@ -111,3 +116,28 @@ client.foo.useCreate(); client.foo.useUpdate(); client.bar.useCreate(); + +// procedures (query) +check(proceduresClient.$procs.greet.useQuery().data?.toUpperCase()); +check(proceduresClient.$procs.greet.useQuery({ args: { name: 'bob' } }).data?.toUpperCase()); +check(proceduresClient.$procs.greet.useQuery({ args: { name: 'bob' } }, { enabled: true }).queryKey); +// @ts-expect-error wrong arg shape +proceduresClient.$procs.greet.useQuery({ args: { hello: 'world' } }); + +// Infinite queries for procedures are currently disabled, will add back later if needed +// check(proceduresClient.$procs.greetMany.useInfiniteQuery({ args: { name: 'bob' } }).data?.pages[0]?.[0]?.toUpperCase()); +// check(proceduresClient.$procs.greetMany.useInfiniteQuery({ args: { name: 'bob' } }).queryKey); + +// @ts-expect-error missing args +proceduresClient.$procs.greetMany.useQuery(); +// @ts-expect-error greet is not a mutation procedure +proceduresClient.$procs.greet.useMutation(); + +// procedures (mutation) +proceduresClient.$procs.sum.useMutation().mutate({ args: { a: 1, b: 2 } }); +// @ts-expect-error wrong arg shape for multi-param procedure +proceduresClient.$procs.sum.useMutation().mutate([1, 2]); +proceduresClient.$procs.sum + .useMutation() + .mutateAsync({ args: { a: 1, b: 2 } }) + .then((d) => check(d.toFixed(2))); diff --git a/packages/clients/tanstack-query/test/schemas/basic/input.ts b/packages/clients/tanstack-query/test/schemas/basic/input.ts index 766d0ca90..47638e49d 100644 --- a/packages/clients/tanstack-query/test/schemas/basic/input.ts +++ b/packages/clients/tanstack-query/test/schemas/basic/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema-lite"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; @@ -51,6 +53,7 @@ export type PostGetPayload; export type CategoryFindUniqueArgs = $FindUniqueArgs<$Schema, "Category">; export type CategoryFindFirstArgs = $FindFirstArgs<$Schema, "Category">; +export type CategoryExistsArgs = $ExistsArgs<$Schema, "Category">; export type CategoryCreateArgs = $CreateArgs<$Schema, "Category">; export type CategoryCreateManyArgs = $CreateManyArgs<$Schema, "Category">; export type CategoryCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Category">; @@ -71,6 +74,7 @@ export type CategoryGetPayload; export type FooFindUniqueArgs = $FindUniqueArgs<$Schema, "Foo">; export type FooFindFirstArgs = $FindFirstArgs<$Schema, "Foo">; +export type FooExistsArgs = $ExistsArgs<$Schema, "Foo">; export type FooCreateArgs = $CreateArgs<$Schema, "Foo">; export type FooCreateManyArgs = $CreateManyArgs<$Schema, "Foo">; export type FooCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Foo">; @@ -91,6 +95,7 @@ export type FooGetPayload, export type BarFindManyArgs = $FindManyArgs<$Schema, "Bar">; export type BarFindUniqueArgs = $FindUniqueArgs<$Schema, "Bar">; export type BarFindFirstArgs = $FindFirstArgs<$Schema, "Bar">; +export type BarExistsArgs = $ExistsArgs<$Schema, "Bar">; export type BarCreateArgs = $CreateArgs<$Schema, "Bar">; export type BarCreateManyArgs = $CreateManyArgs<$Schema, "Bar">; export type BarCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Bar">; diff --git a/packages/clients/tanstack-query/test/schemas/procedures/schema-lite.ts b/packages/clients/tanstack-query/test/schemas/procedures/schema-lite.ts new file mode 100644 index 000000000..630d31410 --- /dev/null +++ b/packages/clients/tanstack-query/test/schemas/procedures/schema-lite.ts @@ -0,0 +1,60 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// NOTE: Test fixture schema used for TanStack Query typing tests. // +////////////////////////////////////////////////////////////////////////////////////////////// + +import { type SchemaDef, ExpressionUtils } from '@zenstackhq/orm/schema'; + +export class SchemaType implements SchemaDef { + provider = { + type: 'sqlite', + } as const; + + models = { + User: { + name: 'User', + fields: { + id: { + name: 'id', + type: 'String', + id: true, + default: ExpressionUtils.call('cuid'), + }, + email: { + name: 'email', + type: 'String', + unique: true, + }, + }, + idFields: ['id'], + uniqueFields: { + id: { type: 'String' }, + email: { type: 'String' }, + }, + }, + } as const; + + procedures = { + greet: { + params: { name: { name: 'name', type: 'String', optional: true } }, + returnType: 'String', + }, + greetMany: { + params: { name: { name: 'name', type: 'String' } }, + returnType: 'String', + returnArray: true, + }, + sum: { + params: { + a: { name: 'a', type: 'Int' }, + b: { name: 'b', type: 'Int' }, + }, + returnType: 'Int', + mutation: true, + }, + } as const; + + authType = 'User' as const; + plugins = {}; +} + +export const schema = new SchemaType(); diff --git a/packages/clients/tanstack-query/test/svelte-typing-test.ts b/packages/clients/tanstack-query/test/svelte-typing-test.ts index 9c8788ebf..eda2add95 100644 --- a/packages/clients/tanstack-query/test/svelte-typing-test.ts +++ b/packages/clients/tanstack-query/test/svelte-typing-test.ts @@ -1,14 +1,21 @@ import { useClientQueries } from '../src/svelte/index.svelte'; import { schema } from './schemas/basic/schema-lite'; +import { schema as proceduresSchema } from './schemas/procedures/schema-lite'; const client = useClientQueries(schema); +const proceduresClient = useClientQueries(proceduresSchema); // @ts-expect-error missing args client.user.useFindUnique(); check(client.user.useFindUnique(() => ({ where: { id: '1' } })).data?.email); check(client.user.useFindUnique(() => ({ where: { id: '1' } })).queryKey); -check(client.user.useFindUnique(() => ({ where: { id: '1' } }), () => ({ optimisticUpdate: true, enabled: false }))); +check( + client.user.useFindUnique( + () => ({ where: { id: '1' } }), + () => ({ optimisticUpdate: true, enabled: false }), + ), +); // @ts-expect-error unselected field check(client.user.useFindUnique(() => ({ select: { email: true }, where: { id: '1' } })).data?.name); @@ -18,6 +25,9 @@ check(client.user.useFindUnique(() => ({ where: { id: '1' }, include: { posts: t check(client.user.useFindFirst().data?.email); check(client.user.useFindFirst().data?.$optimistic); +check(client.user.useExists().data); +check(client.user.useExists(() => ({ where: { id: '1' } })).data); + check(client.user.useFindMany().data?.[0]?.email); check(client.user.useFindMany().data?.[0]?.$optimistic); @@ -43,28 +53,34 @@ check(client.user.useGroupBy(() => ({ by: ['email'], _max: { name: true } })).da // @ts-expect-error missing args client.user.useCreate().mutate(); client.user.useCreate().mutate({ data: { email: 'test@example.com' } }); -client.user.useCreate(() => ({ optimisticUpdate: true, invalidateQueries: false, retry: 3 })).mutate({ - data: { email: 'test@example.com' }, -}); - -client.user.useCreate() +client.user + .useCreate(() => ({ optimisticUpdate: true, invalidateQueries: false, retry: 3 })) + .mutate({ + data: { email: 'test@example.com' }, + }); + +client.user + .useCreate() .mutateAsync({ data: { email: 'test@example.com' }, include: { posts: true } }) .then((d) => check(d.posts[0]?.title)); -client.user.useCreateMany() +client.user + .useCreateMany() .mutateAsync({ data: [{ email: 'test@example.com' }, { email: 'test2@example.com' }], skipDuplicates: true, }) .then((d) => d.count); -client.user.useCreateManyAndReturn() +client.user + .useCreateManyAndReturn() .mutateAsync({ data: [{ email: 'test@example.com' }], }) .then((d) => check(d[0]?.name)); -client.user.useCreateManyAndReturn() +client.user + .useCreateManyAndReturn() .mutateAsync({ data: [{ email: 'test@example.com' }], select: { email: true }, @@ -83,7 +99,8 @@ client.user.useUpdate().mutate( client.user.useUpdateMany().mutate({ data: { email: 'updated@example.com' } }); -client.user.useUpdateManyAndReturn() +client.user + .useUpdateManyAndReturn() .mutateAsync({ data: { email: 'updated@example.com' } }) .then((d) => check(d[0]?.email)); @@ -106,3 +123,28 @@ client.foo.useCreate(); client.foo.useUpdate(); client.bar.useCreate(); + +// procedures (query) +check(proceduresClient.$procs.greet.useQuery().data?.toUpperCase()); +check(proceduresClient.$procs.greet.useQuery(() => ({ args: { name: 'bob' } })).data?.toUpperCase()); +check(proceduresClient.$procs.greet.useQuery(() => ({ args: { name: 'bob' } })).queryKey); + +// Infinite queries for procedures are currently disabled, will add back later if needed +// check( +// proceduresClient.$procs.greetMany +// .useInfiniteQuery(() => ({ args: { name: 'bob' } })) +// .data?.pages[0]?.[0]?.toUpperCase(), +// ); +// check(proceduresClient.$procs.greetMany.useInfiniteQuery(() => ({ args: { name: 'bob' } })).queryKey); + +// @ts-expect-error greet is not a mutation procedure +proceduresClient.$procs.greet.useMutation(); + +// procedures (mutation) +proceduresClient.$procs.sum.useMutation().mutate({ args: { a: 1, b: 2 } }); +// @ts-expect-error wrong arg shape for multi-param procedure +proceduresClient.$procs.sum.useMutation().mutate([1, 2]); +proceduresClient.$procs.sum + .useMutation() + .mutateAsync({ args: { a: 1, b: 2 } }) + .then((d) => check(d.toFixed(2))); diff --git a/packages/clients/tanstack-query/test/vue-typing-test.ts b/packages/clients/tanstack-query/test/vue-typing-test.ts index f134378cb..60099fb2f 100644 --- a/packages/clients/tanstack-query/test/vue-typing-test.ts +++ b/packages/clients/tanstack-query/test/vue-typing-test.ts @@ -1,7 +1,9 @@ import { useClientQueries } from '../src/vue'; import { schema } from './schemas/basic/schema-lite'; +import { schema as proceduresSchema } from './schemas/procedures/schema-lite'; const client = useClientQueries(schema); +const proceduresClient = useClientQueries(proceduresSchema); // @ts-expect-error missing args client.user.useFindUnique(); @@ -18,6 +20,9 @@ check(client.user.useFindUnique({ where: { id: '1' }, include: { posts: true } } check(client.user.useFindFirst().data.value?.email); check(client.user.useFindFirst().data.value?.$optimistic); +check(client.user.useExists().data.value); +check(client.user.useExists({ where: { id: '1' } }).data.value); + check(client.user.useFindMany().data.value?.[0]?.email); check(client.user.useFindMany().data.value?.[0]?.$optimistic); @@ -109,3 +114,26 @@ client.foo.useCreate(); client.foo.useUpdate(); client.bar.useCreate(); + +// procedures (query) +check(proceduresClient.$procs.greet.useQuery().data.value?.toUpperCase()); +check(proceduresClient.$procs.greet.useQuery({ args: { name: 'bob' } }).data.value?.toUpperCase()); +check(proceduresClient.$procs.greet.useQuery({ args: { name: 'bob' } }).queryKey.value); + +// Infinite queries for procedures are currently disabled, will add back later if needed +// check( +// proceduresClient.$procs.greetMany.useInfiniteQuery({ args: { name: 'bob' } }).data.value?.pages[0]?.[0]?.toUpperCase(), +// ); +// check(proceduresClient.$procs.greetMany.useInfiniteQuery({ args: { name: 'bob' } }).queryKey.value); + +// @ts-expect-error greet is not a mutation procedure +proceduresClient.$procs.greet.useMutation(); + +// procedures (mutation) +proceduresClient.$procs.sum.useMutation().mutate({ args: { a: 1, b: 2 } }); +// @ts-expect-error wrong arg shape for multi-param procedure +proceduresClient.$procs.sum.useMutation().mutate([1, 2]); +proceduresClient.$procs.sum + .useMutation() + .mutateAsync({ args: { a: 1, b: 2 } }) + .then((d) => check(d.toFixed(2))); diff --git a/packages/common-helpers/package.json b/packages/common-helpers/package.json index f26a410f4..a12480c8e 100644 --- a/packages/common-helpers/package.json +++ b/packages/common-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/common-helpers", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack Common Helpers", "type": "module", "scripts": { diff --git a/packages/common-helpers/src/index.ts b/packages/common-helpers/src/index.ts index 07c4fff56..146609fb7 100644 --- a/packages/common-helpers/src/index.ts +++ b/packages/common-helpers/src/index.ts @@ -4,6 +4,7 @@ export * from './is-plain-object'; export * from './lower-case-first'; export * from './param-case'; export * from './safe-json-stringify'; +export * from './single-debounce'; export * from './sleep'; export * from './tiny-invariant'; export * from './upper-case-first'; diff --git a/packages/common-helpers/src/single-debounce.ts b/packages/common-helpers/src/single-debounce.ts new file mode 100644 index 000000000..f16091e5c --- /dev/null +++ b/packages/common-helpers/src/single-debounce.ts @@ -0,0 +1,34 @@ +export function singleDebounce(cb: () => void | PromiseLike, debounceMc: number, reRunOnInProgressCall: boolean = false) { + let timeout: ReturnType | undefined; + let inProgress = false; + let pendingInProgress = false; + + const run = async () => { + if (inProgress) { + if (reRunOnInProgressCall) { + pendingInProgress = true; + } + + return; + } + + inProgress = true; + pendingInProgress = false; + + try { + await cb(); + } finally { + inProgress = false; + + if (pendingInProgress) { + await run(); + } + } + }; + + return () => { + clearTimeout(timeout); + + timeout = setTimeout(run, debounceMc); + } +} diff --git a/packages/config/eslint-config/package.json b/packages/config/eslint-config/package.json index 096680bf5..d10f4f05d 100644 --- a/packages/config/eslint-config/package.json +++ b/packages/config/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/eslint-config", - "version": "3.1.1", + "version": "3.2.0", "type": "module", "private": true, "license": "MIT" diff --git a/packages/config/typescript-config/package.json b/packages/config/typescript-config/package.json index 08cb430dd..9e05c2300 100644 --- a/packages/config/typescript-config/package.json +++ b/packages/config/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/typescript-config", - "version": "3.1.1", + "version": "3.2.0", "private": true, "license": "MIT" } diff --git a/packages/config/vitest-config/package.json b/packages/config/vitest-config/package.json index fe34a2954..addc8dc99 100644 --- a/packages/config/vitest-config/package.json +++ b/packages/config/vitest-config/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/vitest-config", "type": "module", - "version": "3.1.1", + "version": "3.2.0", "private": true, "license": "MIT", "exports": { diff --git a/packages/create-zenstack/package.json b/packages/create-zenstack/package.json index 8eda8d763..e314b5d7c 100644 --- a/packages/create-zenstack/package.json +++ b/packages/create-zenstack/package.json @@ -1,6 +1,6 @@ { "name": "create-zenstack", - "version": "3.1.1", + "version": "3.2.0", "description": "Create a new ZenStack project", "type": "module", "scripts": { diff --git a/packages/ide/vscode/package.json b/packages/ide/vscode/package.json index dee8389bc..6f99cde12 100644 --- a/packages/ide/vscode/package.json +++ b/packages/ide/vscode/package.json @@ -1,7 +1,7 @@ { "name": "zenstack-v3", "publisher": "zenstack", - "version": "3.0.15", + "version": "3.2.0", "displayName": "ZenStack V3 Language Tools", "description": "VSCode extension for ZenStack (v3) ZModel language", "private": true, diff --git a/packages/language/package.json b/packages/language/package.json index 37eb20690..091920dbd 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/language", "description": "ZenStack ZModel language specification", - "version": "3.1.1", + "version": "3.2.0", "license": "MIT", "author": "ZenStack Team", "files": [ diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index e759aa1fd..7d6a589cc 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -53,7 +53,9 @@ export type ZModelKeywordNames = | "Object" | "String" | "TransitiveFieldReference" + | "Undefined" | "Unsupported" + | "Void" | "[" | "]" | "^" @@ -120,10 +122,10 @@ export function isExpression(item: unknown): item is Expression { return reflection.isInstance(item, Expression); } -export type ExpressionType = 'Any' | 'Boolean' | 'DateTime' | 'Float' | 'Int' | 'Null' | 'Object' | 'String' | 'Unsupported'; +export type ExpressionType = 'Any' | 'BigInt' | 'Boolean' | 'Bytes' | 'DateTime' | 'Decimal' | 'Float' | 'Int' | 'Json' | 'Null' | 'Object' | 'String' | 'Undefined' | 'Unsupported' | 'Void'; export function isExpressionType(item: unknown): item is ExpressionType { - return item === 'String' || item === 'Int' || item === 'Float' || item === 'Boolean' || item === 'DateTime' || item === 'Null' || item === 'Object' || item === 'Any' || item === 'Unsupported'; + return item === 'String' || item === 'Int' || item === 'Float' || item === 'Boolean' || item === 'BigInt' || item === 'Decimal' || item === 'DateTime' || item === 'Json' || item === 'Bytes' || item === 'Null' || item === 'Object' || item === 'Any' || item === 'Void' || item === 'Undefined' || item === 'Unsupported'; } export type LiteralExpr = BooleanLiteral | NumberLiteral | StringLiteral; @@ -156,10 +158,10 @@ export function isRegularID(item: unknown): item is RegularID { return item === 'model' || item === 'enum' || item === 'attribute' || item === 'datasource' || item === 'plugin' || item === 'abstract' || item === 'in' || item === 'view' || item === 'import' || item === 'type' || (typeof item === 'string' && (/[_a-zA-Z][\w_]*/.test(item))); } -export type RegularIDWithTypeNames = 'Any' | 'BigInt' | 'Boolean' | 'Bytes' | 'DateTime' | 'Decimal' | 'Float' | 'Int' | 'Json' | 'Null' | 'Object' | 'String' | 'Unsupported' | RegularID; +export type RegularIDWithTypeNames = 'Any' | 'BigInt' | 'Boolean' | 'Bytes' | 'DateTime' | 'Decimal' | 'Float' | 'Int' | 'Json' | 'Null' | 'Object' | 'String' | 'Unsupported' | 'Void' | RegularID; export function isRegularIDWithTypeNames(item: unknown): item is RegularIDWithTypeNames { - return isRegularID(item) || item === 'String' || item === 'Boolean' || item === 'Int' || item === 'BigInt' || item === 'Float' || item === 'Decimal' || item === 'DateTime' || item === 'Json' || item === 'Bytes' || item === 'Null' || item === 'Object' || item === 'Any' || item === 'Unsupported'; + return isRegularID(item) || item === 'String' || item === 'Boolean' || item === 'Int' || item === 'BigInt' || item === 'Float' || item === 'Decimal' || item === 'DateTime' || item === 'Json' || item === 'Bytes' || item === 'Null' || item === 'Object' || item === 'Any' || item === 'Void' || item === 'Unsupported'; } export type TypeDeclaration = DataModel | Enum | TypeDef; @@ -477,7 +479,7 @@ export function isFunctionDecl(item: unknown): item is FunctionDecl { } export interface FunctionParam extends langium.AstNode { - readonly $container: FunctionDecl | Procedure; + readonly $container: FunctionDecl; readonly $type: 'FunctionParam'; name: RegularID; optional: boolean; @@ -648,7 +650,7 @@ export interface Procedure extends langium.AstNode { attributes: Array; mutation: boolean; name: RegularID; - params: Array; + params: Array; returnType: FunctionParamType; } diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index 02260ccd7..53688b82f 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -2927,7 +2927,8 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "Keyword", "value": "mutation" - } + }, + "cardinality": "?" }, { "$type": "Keyword", @@ -2978,7 +2979,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@47" + "$ref": "#/rules@49" }, "arguments": [] } @@ -3156,6 +3157,10 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "$type": "Keyword", "value": "Any" }, + { + "$type": "Keyword", + "value": "Void" + }, { "$type": "Keyword", "value": "Unsupported" @@ -3766,10 +3771,26 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "$type": "Keyword", "value": "Boolean" }, + { + "$type": "Keyword", + "value": "BigInt" + }, + { + "$type": "Keyword", + "value": "Decimal" + }, { "$type": "Keyword", "value": "DateTime" }, + { + "$type": "Keyword", + "value": "Json" + }, + { + "$type": "Keyword", + "value": "Bytes" + }, { "$type": "Keyword", "value": "Null" @@ -3782,6 +3803,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "$type": "Keyword", "value": "Any" }, + { + "$type": "Keyword", + "value": "Void" + }, + { + "$type": "Keyword", + "value": "Undefined" + }, { "$type": "Keyword", "value": "Unsupported" diff --git a/packages/language/src/utils.ts b/packages/language/src/utils.ts index 4b489fd52..a99f160cb 100644 --- a/packages/language/src/utils.ts +++ b/packages/language/src/utils.ts @@ -105,8 +105,8 @@ export function typeAssignable(destType: ExpressionType, sourceType: ExpressionT * Maps a ZModel builtin type to expression type */ export function mapBuiltinTypeToExpressionType( - type: BuiltinType | 'Any' | 'Object' | 'Null' | 'Unsupported', -): ExpressionType | 'Any' { + type: BuiltinType | ExpressionType, +): ExpressionType { switch (type) { case 'Any': case 'Boolean': @@ -115,6 +115,10 @@ export function mapBuiltinTypeToExpressionType( case 'Int': case 'Float': case 'Null': + case 'Object': + case 'Unsupported': + case 'Void': + case 'Undefined': return type; case 'BigInt': return 'Int'; @@ -123,10 +127,6 @@ export function mapBuiltinTypeToExpressionType( case 'Json': case 'Bytes': return 'Any'; - case 'Object': - return 'Object'; - case 'Unsupported': - return 'Unsupported'; } } @@ -149,6 +149,13 @@ export function isRelationshipField(field: DataField) { return isDataModel(field.type.reference?.ref); } +/** + * Returns if the given field is a computed field. + */ +export function isComputedField(field: DataField) { + return hasAttribute(field, '@computed'); +} + export function isDelegateModel(node: AstNode) { return isDataModel(node) && hasAttribute(node, '@@delegate'); } diff --git a/packages/language/src/validator.ts b/packages/language/src/validator.ts index dcbf549f0..8663e90f0 100644 --- a/packages/language/src/validator.ts +++ b/packages/language/src/validator.ts @@ -9,6 +9,7 @@ import type { GeneratorDecl, InvocationExpr, Model, + Procedure, TypeDef, ZModelAstType, } from './generated/ast'; @@ -20,6 +21,7 @@ import EnumValidator from './validators/enum-validator'; import ExpressionValidator from './validators/expression-validator'; import FunctionDeclValidator from './validators/function-decl-validator'; import FunctionInvocationValidator from './validators/function-invocation-validator'; +import ProcedureValidator from './validators/procedure-validator'; import SchemaValidator from './validators/schema-validator'; import TypeDefValidator from './validators/typedef-validator'; @@ -40,6 +42,7 @@ export function registerValidationChecks(services: ZModelServices) { Expression: validator.checkExpression, InvocationExpr: validator.checkFunctionInvocation, FunctionDecl: validator.checkFunctionDecl, + Procedure: validator.checkProcedure, }; registry.register(checks, validator); } @@ -89,4 +92,9 @@ export class ZModelValidator { checkFunctionDecl(node: FunctionDecl, accept: ValidationAcceptor): void { new FunctionDeclValidator().validate(node, accept); } + + checkProcedure(node: Procedure, accept: ValidationAcceptor): void { + new ProcedureValidator().validate(node, accept); + } } + diff --git a/packages/language/src/validators/attribute-application-validator.ts b/packages/language/src/validators/attribute-application-validator.ts index 62df3a235..74ea33ec7 100644 --- a/packages/language/src/validators/attribute-application-validator.ts +++ b/packages/language/src/validators/attribute-application-validator.ts @@ -33,6 +33,7 @@ import { isAuthOrAuthMemberAccess, isBeforeInvocation, isCollectionPredicate, + isComputedField, isDataFieldReference, isDelegateModel, isRelationshipField, @@ -261,23 +262,22 @@ export default class AttributeApplicationValidator implements AstValidator isBeforeInvocation(node))) { - accept('error', `"before()" is not allowed in field-level policy rules`, { node: expr }); + accept('error', `"before()" is not allowed in field-level policies`, { node: expr }); } - // 'update' rules are not allowed for relation fields - if (kindItems.includes('update') || kindItems.includes('all')) { - const field = attr.$container as DataField; - if (isRelationshipField(field)) { - accept( - 'error', - `Field-level policy rules with "update" or "all" kind are not allowed for relation fields. Put rules on foreign-key fields instead.`, - { node: attr }, - ); - } + // relation fields are not allowed + const field = attr.$container as DataField; + + if (isRelationshipField(field)) { + accept('error', `Field-level policies are not allowed for relation fields.`, { node: attr }); + } + + if (isComputedField(field)) { + accept('error', `Field-level policies are not allowed for computed fields.`, { node: attr }); } } diff --git a/packages/language/src/validators/common.ts b/packages/language/src/validators/common.ts index 257efc79c..01aad460d 100644 --- a/packages/language/src/validators/common.ts +++ b/packages/language/src/validators/common.ts @@ -20,10 +20,11 @@ export function validateDuplicatedDeclarations( accept: ValidationAcceptor, ): void { const groupByName = decls.reduce>>((group, decl) => { + // Use a null-prototype map to avoid issues with names like "__proto__"/"constructor". group[decl.name] = group[decl.name] ?? []; group[decl.name]!.push(decl); return group; - }, {}); + }, Object.create(null) as Record>); for (const [name, decls] of Object.entries(groupByName)) { if (decls.length > 1) { diff --git a/packages/language/src/validators/function-invocation-validator.ts b/packages/language/src/validators/function-invocation-validator.ts index a86665656..8ce1035e3 100644 --- a/packages/language/src/validators/function-invocation-validator.ts +++ b/packages/language/src/validators/function-invocation-validator.ts @@ -20,6 +20,7 @@ import { getLiteral, isCheckInvocation, isDataFieldReference, + mapBuiltinTypeToExpressionType, typeAssignable, } from '../utils'; import type { AstValidator } from './common'; @@ -173,7 +174,9 @@ export default class FunctionInvocationValidator implements AstValidator { + validate(proc: Procedure, accept: ValidationAcceptor): void { + this.validateName(proc, accept); + proc.attributes.forEach((attr) => validateAttributeApplication(attr, accept)); + } + + private validateName(proc: Procedure, accept: ValidationAcceptor): void { + if (RESERVED_PROCEDURE_NAMES.has(proc.name)) { + accept('error', `Procedure name "${proc.name}" is reserved`, { + node: proc, + property: 'name', + }); + } + } +} diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index 8d2797870..9f09dbf89 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -226,7 +226,7 @@ ProcedureParam: TRIPLE_SLASH_COMMENT* name=RegularID ':' type=FunctionParamType (optional?='?')?; Procedure: - TRIPLE_SLASH_COMMENT* (mutation?='mutation') 'procedure' name=RegularID '(' (params+=ProcedureParam (',' params+=FunctionParam)*)? ')' ':' returnType=FunctionParamType (attributes+=InternalAttribute)*; + TRIPLE_SLASH_COMMENT* (mutation?='mutation')? 'procedure' name=RegularID '(' (params+=ProcedureParam (',' params+=ProcedureParam)*)? ')' ':' returnType=FunctionParamType (attributes+=InternalAttribute)*; // https://github.com/langium/langium/discussions/1012 RegularID returns string: @@ -234,7 +234,7 @@ RegularID returns string: ID | 'model' | 'enum' | 'attribute' | 'datasource' | 'plugin' | 'abstract' | 'in' | 'view' | 'import' | 'type'; RegularIDWithTypeNames returns string: - RegularID | 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes' | 'Null' | 'Object' | 'Any' | 'Unsupported'; + RegularID | 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes' | 'Null' | 'Object' | 'Any' | 'Void' | 'Unsupported'; // attribute Attribute: @@ -266,7 +266,7 @@ AttributeArg: (name=RegularID ':')? value=Expression; ExpressionType returns string: - 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Null' | 'Object' | 'Any' | 'Unsupported'; + 'String' | 'Int' | 'Float' | 'Boolean' | 'BigInt' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes' | 'Null' | 'Object' | 'Any' | 'Void' | 'Undefined' | 'Unsupported'; BuiltinType returns string: 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes'; diff --git a/packages/language/test/attribute-application.test.ts b/packages/language/test/attribute-application.test.ts index ce17ce499..7dde410b7 100644 --- a/packages/language/test/attribute-application.test.ts +++ b/packages/language/test/attribute-application.test.ts @@ -1,24 +1,434 @@ import { describe, it } from 'vitest'; -import { loadSchemaWithError } from './utils'; +import { loadSchema, loadSchemaWithError } from './utils'; describe('Attribute application validation tests', () => { - it('rejects before in non-post-update policies', async () => { - await loadSchemaWithError( - ` - datasource db { - provider = 'sqlite' - url = 'file:./dev.db' - } - - model Foo { - id Int @id @default(autoincrement()) - x Int - @@allow('all', true) - @@deny('update', before(x) > 2) - } - `, - `"before()" is only allowed in "post-update" policy rules`, - ); + describe('Model-level policy attributes (@@allow, @@deny)', () => { + it('accepts valid policy kinds', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + @@allow('create', true) + @@allow('read', true) + @@allow('update', true) + @@allow('post-update', true) + @@allow('delete', true) + @@deny('all', false) + } + `); + }); + + it('accepts comma-separated policy kinds', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + @@allow('create, read, update', true) + @@deny('delete, post-update', false) + } + `); + }); + + it('rejects invalid policy kind', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + @@allow('invalid', true) + } + `, + `Invalid policy rule kind`, + ); + }); + + it('rejects before in create policies', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + @@allow('all', true) + @@deny('create', before(x) > 2) + } + `, + `"before()" is only allowed in "post-update" policy rules`, + ); + }); + + it('rejects before in read policies', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + @@allow('all', true) + @@deny('read', before(x) > 2) + } + `, + `"before()" is only allowed in "post-update" policy rules`, + ); + }); + + it('rejects before in non-post-update policies', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + @@allow('all', true) + @@deny('update', before(x) > 2) + } + `, + `"before()" is only allowed in "post-update" policy rules`, + ); + }); + + it('rejects before in delete policies', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + @@allow('all', true) + @@deny('delete', before(x) > 2) + } + `, + `"before()" is only allowed in "post-update" policy rules`, + ); + }); + + it('accepts before in post-update policies', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + @@allow('all', true) + @@deny('post-update', before().x > 2) + } + `); + }); + + it('rejects non-owned relation in create policy', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('create', bars?[x > 0]) + } + + model Bar { + id Int @id @default(autoincrement()) + x Int + foo Foo @relation(fields: [fooId], references: [id]) + fooId Int + @@allow('all', true) + } + `, + `non-owned relation fields are not allowed in "create" rules`, + ); + }); + + it('rejects non-owned relation in all policy', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('all', bars?[x > 0]) + } + + model Bar { + id Int @id @default(autoincrement()) + x Int + foo Foo @relation(fields: [fooId], references: [id]) + fooId Int + @@allow('all', true) + } + `, + `non-owned relation fields are not allowed in "create" rules`, + ); + }); + + it('accepts owned relation in create policy', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bar Bar @relation(fields: [barId], references: [id]) + barId Int + @@allow('create', bar.x > 0) + } + + model Bar { + id Int @id @default(autoincrement()) + x Int + foos Foo[] + @@allow('all', true) + } + `); + }); + + it('accepts non-owned relation in read policy', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('read', bars?[x > 0]) + } + + model Bar { + id Int @id @default(autoincrement()) + x Int + foo Foo @relation(fields: [fooId], references: [id]) + fooId Int + @@allow('all', true) + } + `); + }); + + it('accepts non-owned relation in update policy', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bars Bar[] + @@allow('update', bars?[x > 0]) + } + + model Bar { + id Int @id @default(autoincrement()) + x Int + foo Foo @relation(fields: [fooId], references: [id]) + fooId Int + @@allow('all', true) + } + `); + }); + + it('accepts auth() relation access in create policy', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model User { + id Int @id @default(autoincrement()) + posts Post[] + @@allow('all', true) + @@auth() + } + + model Post { + id Int @id @default(autoincrement()) + author User @relation(fields: [authorId], references: [id]) + authorId Int + @@allow('create', auth().posts?[id > 0]) + } + `); + }); + }); + + describe('Field-level policy attributes (@allow, @deny)', () => { + it('accepts valid field-level policy kinds', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int @allow('read', true) + y Int @allow('update', true) + z Int @deny('all', false) + @@allow('all', true) + } + `); + }); + + it('accepts comma-separated field-level policy kinds', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int @allow('read, update', true) + @@allow('all', true) + } + `); + }); + + it('rejects before in field-level policies', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int @deny('update', before(x) > 2) + @@allow('all', true) + } + `, + `"before()" is not allowed in field-level policies`, + ); + }); + + it('rejects field-level policy on relation fields', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + bar Bar @relation(fields: [barId], references: [id]) @allow('read', true) + barId Int + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + foos Foo[] + @@allow('all', true) + } + `, + `Field-level policies are not allowed for relation fields`, + ); + }); + + it('rejects field-level policy on computed fields', async () => { + await loadSchemaWithError( + ` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + doubled Int @computed @allow('read', true) + @@allow('all', true) + } + `, + `Field-level policies are not allowed for computed fields`, + ); + }); + + it('accepts field-level policy on regular fields', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model Foo { + id Int @id @default(autoincrement()) + x Int @allow('read', true) + y String @deny('update', false) + @@allow('all', true) + } + `); + }); + + it('accepts complex expressions in field-level policies', async () => { + await loadSchema(` + datasource db { + provider = 'sqlite' + url = 'file:./dev.db' + } + + model User { + id Int @id @default(autoincrement()) + email String + posts Post[] + @@allow('all', true) + @@auth() + } + + model Post { + id Int @id @default(autoincrement()) + title String @allow('update', auth() != null && auth().id == authorId) + author User @relation(fields: [authorId], references: [id]) + authorId Int + @@allow('all', true) + } + `); + }); }); it('requires relation and fk to have consistent optionality', async () => { @@ -28,14 +438,14 @@ describe('Attribute application validation tests', () => { provider = 'sqlite' url = 'file:./dev.db' } - + model Foo { id Int @id @default(autoincrement()) bar Bar @relation(fields: [barId], references: [id]) barId Int? @@allow('all', true) } - + model Bar { id Int @id @default(autoincrement()) foos Foo[] diff --git a/packages/language/test/procedure-validation.test.ts b/packages/language/test/procedure-validation.test.ts new file mode 100644 index 000000000..a830ac661 --- /dev/null +++ b/packages/language/test/procedure-validation.test.ts @@ -0,0 +1,43 @@ +import { describe, it } from 'vitest'; +import { loadSchemaWithError } from './utils'; + +describe('Procedure validation', () => { + it('rejects unknown parameter type', async () => { + await loadSchemaWithError( + ` +model User { + id Int @id +} + +procedure foo(a: NotAType): Int + `, + /unknown type|could not resolve reference/i, + ); + }); + + it('rejects unknown return type', async () => { + await loadSchemaWithError( + ` +model User { + id Int @id +} + +procedure foo(): NotAType + `, + /unknown type|could not resolve reference/i, + ); + }); + + it('rejects reserved procedure names', async () => { + await loadSchemaWithError( + ` +model User { + id Int @id +} + +procedure __proto__(): Int + `, + /reserved/i, + ); + }); +}); diff --git a/packages/orm/package.json b/packages/orm/package.json index 7efb44c15..173c16fd1 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/orm", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack ORM", "type": "module", "scripts": { @@ -90,6 +90,7 @@ "json-stable-stringify": "^1.3.0", "kysely": "catalog:", "nanoid": "^5.0.9", + "postgres-array": "^3.0.4", "toposort": "^2.0.2", "ts-pattern": "catalog:", "ulid": "^3.0.0", diff --git a/packages/orm/src/client/client-impl.ts b/packages/orm/src/client/client-impl.ts index 8e2c8382b..ce2d4d428 100644 --- a/packages/orm/src/client/client-impl.ts +++ b/packages/orm/src/client/client-impl.ts @@ -27,6 +27,7 @@ import { CountOperationHandler } from './crud/operations/count'; import { CreateOperationHandler } from './crud/operations/create'; import { DeleteOperationHandler } from './crud/operations/delete'; import { FindOperationHandler } from './crud/operations/find'; +import { ExistsOperationHandler } from './crud/operations/exists'; import { GroupByOperationHandler } from './crud/operations/group-by'; import { UpdateOperationHandler } from './crud/operations/update'; import { InputValidator } from './crud/validator'; @@ -214,28 +215,67 @@ export class ClientImpl { } } - get $procedures() { + get $procs() { return Object.keys(this.$schema.procedures ?? {}).reduce((acc, name) => { - acc[name] = (...args: unknown[]) => this.handleProc(name, args); + acc[name] = (input?: unknown) => this.handleProc(name, input); return acc; }, {} as any); } - private async handleProc(name: string, args: unknown[]) { + private async handleProc(name: string, input: unknown) { if (!('procedures' in this.$options) || !this.$options || typeof this.$options.procedures !== 'object') { throw createConfigError('Procedures are not configured for the client.'); } + const procDef = (this.$schema.procedures ?? {})[name]; + if (!procDef) { + throw createConfigError(`Procedure "${name}" is not defined in schema.`); + } + const procOptions = this.$options.procedures as ProceduresOptions< SchemaDef & { procedures: Record; } >; if (!procOptions[name] || typeof procOptions[name] !== 'function') { - throw new Error(`Procedure "${name}" does not have a handler configured.`); + throw createConfigError(`Procedure "${name}" does not have a handler configured.`); } - return (procOptions[name] as Function).apply(this, [this, ...args]); + // Validate inputs using the same validator infrastructure as CRUD operations. + const inputValidator = new InputValidator(this as any); + const validatedInput = inputValidator.validateProcedureInput(name, input); + + const handler = procOptions[name] as Function; + + const invokeWithClient = async (client: any, _input: unknown) => { + let proceed = async (nextInput: unknown) => { + const sanitizedNextInput = + nextInput && typeof nextInput === 'object' && !Array.isArray(nextInput) ? nextInput : {}; + + return handler({ client, ...sanitizedNextInput }); + }; + + // apply plugins + const plugins = [...(client.$options?.plugins ?? [])]; + for (const plugin of plugins) { + const onProcedure = plugin.onProcedure; + if (onProcedure) { + const _proceed = proceed; + proceed = (nextInput: unknown) => + onProcedure({ + client, + name, + mutation: !!procDef.mutation, + input: nextInput, + proceed: (finalInput: unknown) => _proceed(finalInput), + }) as Promise; + } + } + + return proceed(_input); + }; + + return invokeWithClient(this as any, validatedInput); } async $connect() { @@ -598,5 +638,15 @@ function createModelCrudHandler( true, ); }, + + exists: (args: unknown) => { + return createPromise( + 'exists', + 'exists', + args, + new ExistsOperationHandler(client, model, inputValidator), + false, + ); + }, } as ModelOperations; } diff --git a/packages/orm/src/client/contract.ts b/packages/orm/src/client/contract.ts index 050381fd2..af5e51389 100644 --- a/packages/orm/src/client/contract.ts +++ b/packages/orm/src/client/contract.ts @@ -1,4 +1,3 @@ -import type Decimal from 'decimal.js'; import { type FieldIsArray, type GetModels, @@ -10,7 +9,7 @@ import { type SchemaDef, } from '../schema'; import type { AnyKysely } from '../utils/kysely-utils'; -import type { OrUndefinedIf, Simplify, UnwrapTuplePromises } from '../utils/type-utils'; +import type { Simplify, UnwrapTuplePromises } from '../utils/type-utils'; import type { TRANSACTION_UNSUPPORTED_METHODS } from './constants'; import type { AggregateArgs, @@ -24,12 +23,14 @@ import type { DefaultModelResult, DeleteArgs, DeleteManyArgs, + ExistsArgs, FindFirstArgs, FindManyArgs, FindUniqueArgs, + GetProcedureNames, GroupByArgs, GroupByResult, - ModelResult, + ProcedureFunc, SelectSubset, SimplifiedPlainResult, Subset, @@ -194,7 +195,7 @@ export type ClientContract; } & { [Key in GetModels as Uncapitalize]: ModelOperations>; -} & Procedures; +} & ProcedureOperations; /** * The contract for a client in a transaction. @@ -204,41 +205,18 @@ export type TransactionClientContract; -type _TypeMap = { - String: string; - Int: number; - Float: number; - BigInt: bigint; - Decimal: Decimal; - Boolean: boolean; - DateTime: Date; -}; - -type MapType = T extends keyof _TypeMap - ? _TypeMap[T] - : T extends GetModels - ? ModelResult - : unknown; - -export type Procedures = +export type ProcedureOperations = Schema['procedures'] extends Record ? { - $procedures: { - [Key in keyof Schema['procedures']]: ProcedureFunc; + /** + * Custom procedures. + */ + $procs: { + [Key in GetProcedureNames]: ProcedureFunc; }; } : {}; -export type ProcedureFunc = ( - ...args: MapProcedureParams -) => Promise>; - -type MapProcedureParams = { - [P in keyof Params]: Params[P] extends { type: infer U } - ? OrUndefinedIf, Params[P] extends { optional: true } ? true : false> - : never; -}; - /** * Creates a new ZenStack client instance. */ @@ -828,6 +806,27 @@ export type AllModelOperations< groupBy>( args: Subset>, ): ZenStackPromise>>; + + /** + * Checks if an entity exists. + * @param args - exists args + * @returns whether a matching entity was found + * + * @example + * ```ts + * // check if a user exists + * await db.user.exists({ + * where: { id: 1 }, + * }); // result: `boolean` + * + * // check with a relation + * await db.user.exists({ + * where: { posts: { some: { published: true } } }, + * }); // result: `boolean` + */ + exists>( + args?: Subset>, + ): ZenStackPromise; }; export type OperationsIneligibleForDelegateModels = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert'; diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index 1fefdc7dd..071e66744 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -23,6 +23,7 @@ import type { GetTypeDefs, ModelFieldIsOptional, NonRelationFields, + ProcedureDef, RelationFields, RelationFieldType, RelationInfo, @@ -34,16 +35,20 @@ import type { import type { AtLeast, MapBaseType, + MaybePromise, NonEmptyArray, NullableIf, Optional, OrArray, + OrUndefinedIf, PartialIf, Simplify, + TypeMap, ValueOfPotentialTuple, WrapType, XOR, } from '../utils/type-utils'; +import type { ClientContract } from './contract'; import type { QueryOptions } from './options'; import type { ToKyselySchema } from './query-builder'; @@ -1079,6 +1084,8 @@ export type FindManyArgs> = FindArgs; +export type ExistsArgs> = FilterArgs; + export type FindUniqueArgs> = { where: WhereUniqueInput; } & SelectIncludeOmit; @@ -1976,6 +1983,91 @@ type NestedDeleteManyInput< // #endregion +// #region Procedures + +export type GetProcedureNames = Schema extends { procedures: Record } + ? keyof Schema['procedures'] + : never; + +export type GetProcedureParams> = Schema extends { + procedures: Record; +} + ? Schema['procedures'][ProcName]['params'] + : never; + +export type GetProcedure> = Schema extends { + procedures: Record; +} + ? Schema['procedures'][ProcName] + : never; + +type _OptionalProcedureParamNames = keyof { + [K in keyof Params as Params[K] extends { optional: true } ? K : never]: K; +}; + +type _RequiredProcedureParamNames = keyof { + [K in keyof Params as Params[K] extends { optional: true } ? never : K]: K; +}; + +type _HasRequiredProcedureParams = _RequiredProcedureParamNames extends never ? false : true; + +type MapProcedureArgsObject = Simplify< + Optional< + { + [K in keyof Params]: MapProcedureParam; + }, + _OptionalProcedureParamNames + > +>; + +export type ProcedureEnvelope< + Schema extends SchemaDef, + ProcName extends GetProcedureNames, + Params = GetProcedureParams, +> = keyof Params extends never + ? // no params + { args?: Record } + : _HasRequiredProcedureParams extends true + ? // has required params + { args: MapProcedureArgsObject } + : // no required params + { args?: MapProcedureArgsObject }; + +type ProcedureHandlerCtx> = { + client: ClientContract; +} & ProcedureEnvelope; + +/** + * Shape of a procedure's runtime function. + */ +export type ProcedureFunc> = ( + ...args: _HasRequiredProcedureParams> extends true + ? [input: ProcedureEnvelope] + : [input?: ProcedureEnvelope] +) => MaybePromise>>; + +/** + * Signature for procedure handlers configured via client options. + */ +export type ProcedureHandlerFunc> = ( + ctx: ProcedureHandlerCtx, +) => MaybePromise>>; + +type MapProcedureReturn = Proc extends { returnType: infer R } + ? Proc extends { returnArray: true } + ? Array> + : MapType + : never; + +type MapProcedureParam = P extends { type: infer U } + ? OrUndefinedIf< + P extends { array: true } ? Array> : MapType, + P extends { optional: true } ? true : false + > + : never; + +// #endregion + // #region Utilities type NonOwnedRelationFields> = keyof { @@ -1992,6 +2084,21 @@ type HasToManyRelations> = GetEnum[keyof GetEnum< + Schema, + Enum +>]; + +type MapType = T extends keyof TypeMap + ? TypeMap[T] + : T extends GetModels + ? ModelResult + : T extends GetTypeDefs + ? TypeDefResult + : T extends GetEnums + ? EnumValue + : unknown; + // type ProviderSupportsDistinct = Schema['provider']['type'] extends 'postgresql' // ? true // : false; diff --git a/packages/orm/src/client/crud/dialects/base-dialect.ts b/packages/orm/src/client/crud/dialects/base-dialect.ts index 30e330a40..26da88769 100644 --- a/packages/orm/src/client/crud/dialects/base-dialect.ts +++ b/packages/orm/src/client/crud/dialects/base-dialect.ts @@ -48,7 +48,7 @@ export abstract class BaseCrudDialect { return value; } - transformOutput(value: unknown, _type: BuiltinType) { + transformOutput(value: unknown, _type: BuiltinType, _array: boolean) { return value; } diff --git a/packages/orm/src/client/crud/dialects/postgresql.ts b/packages/orm/src/client/crud/dialects/postgresql.ts index 92e570fe8..bae049077 100644 --- a/packages/orm/src/client/crud/dialects/postgresql.ts +++ b/packages/orm/src/client/crud/dialects/postgresql.ts @@ -9,6 +9,7 @@ import { type SelectQueryBuilder, type SqlBool, } from 'kysely'; +import { parse as parsePostgresArray } from 'postgres-array'; import { match } from 'ts-pattern'; import z from 'zod'; import { AnyNullClass, DbNullClass, JsonNullClass } from '../../../common-types'; @@ -20,7 +21,9 @@ import type { ClientOptions } from '../../options'; import { buildJoinPairs, getDelegateDescendantModels, + getEnum, getManyToManyRelation, + isEnum, isRelationField, isTypeDef, requireField, @@ -28,6 +31,7 @@ import { requireModel, } from '../../query-utils'; import { BaseCrudDialect } from './base-dialect'; + export class PostgresCrudDialect extends BaseCrudDialect { private isoDateSchema = z.iso.datetime({ local: true, offset: true }); @@ -70,6 +74,16 @@ export class PostgresCrudDialect extends BaseCrudDiale if (type === 'Json' && !forArrayField) { // scalar `Json` fields need their input stringified return JSON.stringify(value); + } + if (isEnum(this.schema, type)) { + // cast to enum array `CAST(ARRAY[...] AS "enum_type"[])` + return this.eb.cast( + sql`ARRAY[${sql.join( + value.map((v) => this.transformPrimitive(v, type, false)), + sql.raw(','), + )}]`, + this.createSchemaQualifiedEnumType(type, true), + ); } else { // `Json[]` fields need their input as array (not stringified) return value.map((v) => this.transformPrimitive(v, type, false)); @@ -96,7 +110,33 @@ export class PostgresCrudDialect extends BaseCrudDiale } } - override transformOutput(value: unknown, type: BuiltinType) { + private createSchemaQualifiedEnumType(type: string, array: boolean) { + // determines the postgres schema name for the enum type, and returns the + // qualified name + + let qualified = type; + + const enumDef = getEnum(this.schema, type); + if (enumDef) { + // check if the enum has a custom "@@schema" attribute + const schemaAttr = enumDef.attributes?.find((attr) => attr.name === '@@schema'); + if (schemaAttr) { + const mapArg = schemaAttr.args?.find((arg) => arg.name === 'map'); + if (mapArg && mapArg.value.kind === 'literal') { + const schemaName = mapArg.value.value as string; + qualified = `"${schemaName}"."${type}"`; + } + } else { + // no custom schema, use default from datasource or 'public' + const defaultSchema = this.schema.provider.defaultSchema ?? 'public'; + qualified = `"${defaultSchema}"."${type}"`; + } + } + + return array ? sql.raw(`${qualified}[]`) : sql.raw(qualified); + } + + override transformOutput(value: unknown, type: BuiltinType, array: boolean) { if (value === null || value === undefined) { return value; } @@ -105,7 +145,11 @@ export class PostgresCrudDialect extends BaseCrudDiale .with('Bytes', () => this.transformOutputBytes(value)) .with('BigInt', () => this.transformOutputBigInt(value)) .with('Decimal', () => this.transformDecimal(value)) - .otherwise(() => super.transformOutput(value, type)); + .when( + (type) => isEnum(this.schema, type), + () => this.transformOutputEnum(value, array), + ) + .otherwise(() => super.transformOutput(value, type, array)); } private transformOutputBigInt(value: unknown) { @@ -162,6 +206,19 @@ export class PostgresCrudDialect extends BaseCrudDiale : value; } + private transformOutputEnum(value: unknown, array: boolean) { + if (array && typeof value === 'string') { + try { + // postgres returns enum arrays as `{"val 1",val2}` strings, parse them back + // to string arrays here + return parsePostgresArray(value); + } catch { + // fall through - return as-is if parsing fails + } + } + return value; + } + override buildRelationSelection( query: SelectQueryBuilder, model: string, diff --git a/packages/orm/src/client/crud/dialects/sqlite.ts b/packages/orm/src/client/crud/dialects/sqlite.ts index 3f0ae1dc6..32bb4e4ed 100644 --- a/packages/orm/src/client/crud/dialects/sqlite.ts +++ b/packages/orm/src/client/crud/dialects/sqlite.ts @@ -67,7 +67,7 @@ export class SqliteCrudDialect extends BaseCrudDialect } } - override transformOutput(value: unknown, type: BuiltinType) { + override transformOutput(value: unknown, type: BuiltinType, array: boolean) { if (value === null || value === undefined) { return value; } else if (this.schema.typeDefs && type in this.schema.typeDefs) { @@ -81,7 +81,7 @@ export class SqliteCrudDialect extends BaseCrudDialect .with('Decimal', () => this.transformOutputDecimal(value)) .with('BigInt', () => this.transformOutputBigInt(value)) .with('Json', () => this.transformOutputJson(value)) - .otherwise(() => super.transformOutput(value, type)); + .otherwise(() => super.transformOutput(value, type, array)); } } diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index 541689512..957b94ab8 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -69,7 +69,8 @@ export type CoreCrudOperation = | 'deleteMany' | 'count' | 'aggregate' - | 'groupBy'; + | 'groupBy' + | 'exists'; export type AllCrudOperation = CoreCrudOperation | 'findUniqueOrThrow' | 'findFirstOrThrow'; @@ -146,6 +147,32 @@ export abstract class BaseOperationHandler { }); } + protected async existsNonUnique(kysely: ToKysely, model: GetModels, filter: any): Promise { + const query = kysely + .selectNoFrom((eb) => + eb + .exists( + this.dialect + .buildSelectModel(model, model) + .select(sql.lit(1).as('$t')) + .where(() => this.dialect.buildFilter(model, model, filter)), + ) + .as('exists'), + ) + .modifyEnd(this.makeContextComment({ model, operation: 'read' })); + + let result: { exists: number | boolean }[] = []; + const compiled = kysely.getExecutor().compileQuery(query.toOperationNode(), createQueryId()); + try { + const r = await kysely.getExecutor().executeQuery(compiled); + result = r.rows as { exists: number | boolean }[]; + } catch (err) { + throw createDBQueryError(`Failed to execute query: ${err}`, err, compiled.sql, compiled.parameters); + } + + return !!result[0]?.exists; + } + protected async read( kysely: AnyKysely, model: string, @@ -181,7 +208,7 @@ export abstract class BaseOperationHandler { const r = await kysely.getExecutor().executeQuery(compiled); result = r.rows; } catch (err) { - throw createDBQueryError('Failed to execute query', err, compiled.sql, compiled.parameters); + throw createDBQueryError(`Failed to execute query: ${err}`, err, compiled.sql, compiled.parameters); } return result; diff --git a/packages/orm/src/client/crud/operations/exists.ts b/packages/orm/src/client/crud/operations/exists.ts new file mode 100644 index 000000000..d4c6896fc --- /dev/null +++ b/packages/orm/src/client/crud/operations/exists.ts @@ -0,0 +1,12 @@ +import type { SchemaDef } from '../../../schema'; +import { BaseOperationHandler } from './base'; + +export class ExistsOperationHandler extends BaseOperationHandler { + async handle(_operation: 'exists', args: unknown): Promise { + // normalize args to strip `undefined` fields + const normalizedArgs = this.normalizeArgs(args); + const parsedArgs = this.inputValidator.validateExistsArgs(this.model, normalizedArgs); + + return await this.existsNonUnique(this.client.$qb, this.model, parsedArgs?.where); + } +} diff --git a/packages/orm/src/client/crud/validator/index.ts b/packages/orm/src/client/crud/validator/index.ts index 8b16a9de1..50245c605 100644 --- a/packages/orm/src/client/crud/validator/index.ts +++ b/packages/orm/src/client/crud/validator/index.ts @@ -11,6 +11,7 @@ import { type FieldDef, type GetModels, type ModelDef, + type ProcedureDef, type SchemaDef, } from '../../../schema'; import { extractFields } from '../../../utils/object-utils'; @@ -25,6 +26,7 @@ import { type CreateManyArgs, type DeleteArgs, type DeleteManyArgs, + type ExistsArgs, type FindArgs, type GroupByArgs, type UpdateArgs, @@ -39,6 +41,8 @@ import { getEnum, getTypeDef, getUniqueFields, + isEnum, + isTypeDef, requireField, requireModel, } from '../../query-utils'; @@ -70,6 +74,120 @@ export class InputValidator { return this.client.$options.validateInput !== false; } + validateProcedureInput(proc: string, input: unknown): unknown { + const procDef = (this.schema.procedures ?? {})[proc] as ProcedureDef | undefined; + invariant(procDef, `Procedure "${proc}" not found in schema`); + + const params = Object.values(procDef.params ?? {}); + + // For procedures where every parameter is optional, allow omitting the input entirely. + if (typeof input === 'undefined') { + if (params.length === 0) { + return undefined; + } + if (params.every((p) => p.optional)) { + return undefined; + } + throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); + } + + if (typeof input !== 'object') { + throw createInvalidInputError('Procedure input must be an object', `$procs.${proc}`); + } + + const envelope = input as Record; + const argsPayload = Object.prototype.hasOwnProperty.call(envelope, 'args') ? (envelope as any).args : undefined; + + if (params.length === 0) { + if (typeof argsPayload === 'undefined') { + return input; + } + if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { + throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); + } + if (Object.keys(argsPayload as any).length === 0) { + return input; + } + throw createInvalidInputError('Procedure does not accept arguments', `$procs.${proc}`); + } + + if (typeof argsPayload === 'undefined') { + if (params.every((p) => p.optional)) { + return input; + } + throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); + } + + if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { + throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); + } + + const obj = argsPayload as Record; + + for (const param of params) { + const value = (obj as any)[param.name]; + + if (!Object.prototype.hasOwnProperty.call(obj, param.name)) { + if (param.optional) { + continue; + } + throw createInvalidInputError(`Missing procedure argument: ${param.name}`, `$procs.${proc}`); + } + + if (typeof value === 'undefined') { + if (param.optional) { + continue; + } + throw createInvalidInputError( + `Invalid procedure argument: ${param.name} is required`, + `$procs.${proc}`, + ); + } + + const schema = this.makeProcedureParamSchema(param); + const parsed = schema.safeParse(value); + if (!parsed.success) { + throw createInvalidInputError( + `Invalid procedure argument: ${param.name}: ${formatError(parsed.error)}`, + `$procs.${proc}`, + ); + } + } + + return input; + } + + private makeProcedureParamSchema(param: { type: string; array?: boolean; optional?: boolean }): z.ZodType { + let schema: z.ZodType; + + if (isTypeDef(this.schema, param.type)) { + schema = this.makeTypeDefSchema(param.type); + } else if (isEnum(this.schema, param.type)) { + schema = this.makeEnumSchema(param.type); + } else if (param.type in (this.schema.models ?? {})) { + // For model-typed values, accept any object (no deep shape validation). + schema = z.record(z.string(), z.unknown()); + } else { + // Builtin scalar types. + schema = this.makeScalarSchema(param.type as BuiltinType); + + // If a type isn't recognized by any of the above branches, `makeScalarSchema` returns `unknown`. + // Treat it as configuration/schema error. + if (schema instanceof z.ZodUnknown) { + throw createInternalError(`Unsupported procedure parameter type: ${param.type}`); + } + } + + if (param.array) { + schema = schema.array(); + } + if (param.optional) { + schema = schema.optional(); + } + + return schema; + } + validateFindArgs( model: GetModels, args: unknown, @@ -81,6 +199,16 @@ export class InputValidator { >(model, 'find', options, (model, options) => this.makeFindSchema(model, options), args); } + validateExistsArgs(model: GetModels, args: unknown): ExistsArgs> | undefined { + return this.validate>>( + model, + 'exists', + undefined, + (model) => this.makeExistsSchema(model), + args, + ); + } + validateCreateArgs(model: GetModels, args: unknown): CreateArgs> { return this.validate>>( model, @@ -297,6 +425,14 @@ export class InputValidator { return result; } + private makeExistsSchema(model: string) { + return z + .strictObject({ + where: this.makeWhereSchema(model, false).optional(), + }) + .optional(); + } + private makeScalarSchema(type: string, attributes?: readonly AttributeApplication[]) { if (this.schema.typeDefs && type in this.schema.typeDefs) { return this.makeTypeDefSchema(type); @@ -440,7 +576,12 @@ export class InputValidator { if (enumDef) { // enum if (Object.keys(enumDef.values).length > 0) { - fieldSchema = this.makeEnumFilterSchema(enumDef, !!fieldDef.optional, withAggregations); + fieldSchema = this.makeEnumFilterSchema( + enumDef, + !!fieldDef.optional, + withAggregations, + !!fieldDef.array, + ); } } else if (fieldDef.array) { // array field @@ -477,7 +618,12 @@ export class InputValidator { if (enumDef) { // enum if (Object.keys(enumDef.values).length > 0) { - fieldSchema = this.makeEnumFilterSchema(enumDef, !!def.optional, false); + fieldSchema = this.makeEnumFilterSchema( + enumDef, + !!def.optional, + false, + false, + ); } else { fieldSchema = z.never(); } @@ -559,24 +705,23 @@ export class InputValidator { !!fieldDef.array, ).optional(); } else { - // array, enum, primitives - if (fieldDef.array) { + // enum, array, primitives + const enumDef = getEnum(this.schema, fieldDef.type); + if (enumDef) { + fieldSchemas[fieldName] = this.makeEnumFilterSchema( + enumDef, + !!fieldDef.optional, + false, + !!fieldDef.array, + ).optional(); + } else if (fieldDef.array) { fieldSchemas[fieldName] = this.makeArrayFilterSchema(fieldDef.type as BuiltinType).optional(); } else { - const enumDef = getEnum(this.schema, fieldDef.type); - if (enumDef) { - fieldSchemas[fieldName] = this.makeEnumFilterSchema( - enumDef, - !!fieldDef.optional, - false, - ).optional(); - } else { - fieldSchemas[fieldName] = this.makePrimitiveFilterSchema( - fieldDef.type as BuiltinType, - !!fieldDef.optional, - false, - ).optional(); - } + fieldSchemas[fieldName] = this.makePrimitiveFilterSchema( + fieldDef.type as BuiltinType, + !!fieldDef.optional, + false, + ).optional(); } } } @@ -620,12 +765,15 @@ export class InputValidator { return this.schema.typeDefs && type in this.schema.typeDefs; } - private makeEnumFilterSchema(enumDef: EnumDef, optional: boolean, withAggregations: boolean) { + private makeEnumFilterSchema(enumDef: EnumDef, optional: boolean, withAggregations: boolean, array: boolean) { const baseSchema = z.enum(Object.keys(enumDef.values) as [string, ...string[]]); + if (array) { + return this.internalMakeArrayFilterSchema(baseSchema); + } const components = this.makeCommonPrimitiveFilterComponents( baseSchema, optional, - () => z.lazy(() => this.makeEnumFilterSchema(enumDef, optional, withAggregations)), + () => z.lazy(() => this.makeEnumFilterSchema(enumDef, optional, withAggregations, array)), ['equals', 'in', 'notIn', 'not'], withAggregations ? ['_count', '_min', '_max'] : undefined, ); @@ -633,11 +781,15 @@ export class InputValidator { } private makeArrayFilterSchema(type: BuiltinType) { + return this.internalMakeArrayFilterSchema(this.makeScalarSchema(type)); + } + + private internalMakeArrayFilterSchema(elementSchema: ZodType) { return z.strictObject({ - equals: this.makeScalarSchema(type).array().optional(), - has: this.makeScalarSchema(type).optional(), - hasEvery: this.makeScalarSchema(type).array().optional(), - hasSome: this.makeScalarSchema(type).array().optional(), + equals: elementSchema.array().optional(), + has: elementSchema.optional(), + hasEvery: elementSchema.array().optional(), + hasSome: elementSchema.array().optional(), isEmpty: z.boolean().optional(), }); } diff --git a/packages/orm/src/client/executor/name-mapper.ts b/packages/orm/src/client/executor/name-mapper.ts index a3e5938a6..379188756 100644 --- a/packages/orm/src/client/executor/name-mapper.ts +++ b/packages/orm/src/client/executor/name-mapper.ts @@ -78,16 +78,19 @@ export class QueryNameMapper extends OperationNodeTransformer { // process "from" clauses const processedFroms = node.from.froms.map((from) => this.processSelectTable(from)); - // process "join" clauses, note that "from" needs to be added as scopes since join conditions - // can refer to "from" tables - const processedJoins = this.withScopes([...processedFroms.map(({ scope }) => scope)], () => - (node.joins ?? []).map((join) => this.processSelectTable(join.table)), - ); + // process "join" clauses, note that "from" and previous joins need to be added as scopes since join conditions + // can refer to "from" tables and previous joins + const processedJoins: ReturnType[] = []; + const cumulativeScopes = [...processedFroms.map(({ scope }) => scope)]; + for (const join of node.joins ?? []) { + const processedJoin = this.withScopes(cumulativeScopes, () => this.processSelectTable(join.table)); + processedJoins.push(processedJoin); + cumulativeScopes.push(processedJoin.scope); + } // merge the scopes of froms and joins since they're all visible in the query body - const scopes = [...processedFroms.map(({ scope }) => scope), ...processedJoins.map(({ scope }) => scope)]; - return this.withScopes(scopes, () => { + return this.withScopes(cumulativeScopes, () => { // transform join clauses, "on" is transformed within the scopes const joins = node.joins ? node.joins.map((join, i) => ({ @@ -527,9 +530,9 @@ export class QueryNameMapper extends OperationNodeTransformer { let schema = this.schema.provider.defaultSchema ?? 'public'; const schemaAttr = this.schema.models[model]?.attributes?.find((attr) => attr.name === '@@schema'); if (schemaAttr) { - const nameArg = schemaAttr.args?.find((arg) => arg.name === 'map'); - if (nameArg && nameArg.value.kind === 'literal') { - schema = nameArg.value.value as string; + const mapArg = schemaAttr.args?.find((arg) => arg.name === 'map'); + if (mapArg && mapArg.value.kind === 'literal') { + schema = mapArg.value.value as string; } } return schema; diff --git a/packages/orm/src/client/executor/zenstack-query-executor.ts b/packages/orm/src/client/executor/zenstack-query-executor.ts index 6c1cbd5e9..9012cff8b 100644 --- a/packages/orm/src/client/executor/zenstack-query-executor.ts +++ b/packages/orm/src/client/executor/zenstack-query-executor.ts @@ -116,7 +116,7 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor { } else { // wrap error throw createDBQueryError( - 'Failed to execute query', + `Failed to execute query: ${err}`, err, compiledQuery.sql, compiledQuery.parameters, @@ -488,7 +488,12 @@ export class ZenStackQueryExecutor extends DefaultQueryExecutor { try { return await connection.executeQuery(compiledQuery); } catch (err) { - throw createDBQueryError('Failed to execute query', err, compiledQuery.sql, compiledQuery.parameters); + throw createDBQueryError( + `Failed to execute query: ${err}`, + err, + compiledQuery.sql, + compiledQuery.parameters, + ); } } } diff --git a/packages/orm/src/client/index.ts b/packages/orm/src/client/index.ts index 6a3203001..e69e41802 100644 --- a/packages/orm/src/client/index.ts +++ b/packages/orm/src/client/index.ts @@ -3,6 +3,7 @@ export * from './contract'; export type * from './crud-types'; export { getCrudDialect } from './crud/dialects'; export { BaseCrudDialect } from './crud/dialects/base-dialect'; +export { InputValidator } from './crud/validator'; export { ORMError, ORMErrorReason, RejectedByPolicyReason } from './errors'; export * from './options'; export * from './plugin'; diff --git a/packages/orm/src/client/options.ts b/packages/orm/src/client/options.ts index 64f50b613..c5e6c94d3 100644 --- a/packages/orm/src/client/options.ts +++ b/packages/orm/src/client/options.ts @@ -1,7 +1,8 @@ import type { Dialect, Expression, ExpressionBuilder, KyselyConfig } from 'kysely'; import type { GetModel, GetModelFields, GetModels, ProcedureDef, ScalarFields, SchemaDef } from '../schema'; import type { PrependParameter } from '../utils/type-utils'; -import type { ClientContract, CRUD_EXT, ProcedureFunc } from './contract'; +import type { ClientContract, CRUD_EXT } from './contract'; +import type { GetProcedureNames, ProcedureHandlerFunc } from './crud-types'; import type { BaseCrudDialect } from './crud/dialects/base-dialect'; import type { RuntimePlugin } from './plugin'; import type { ToKyselySchema } from './query-builder'; @@ -134,10 +135,7 @@ export type ProceduresOptions = Schema extends { procedures: Record; } ? { - [Key in keyof Schema['procedures']]: PrependParameter< - ClientContract, - ProcedureFunc - >; + [Key in GetProcedureNames]: ProcedureHandlerFunc; } : {}; diff --git a/packages/orm/src/client/plugin.ts b/packages/orm/src/client/plugin.ts index 5664abb81..cd092f4ac 100644 --- a/packages/orm/src/client/plugin.ts +++ b/packages/orm/src/client/plugin.ts @@ -36,6 +36,11 @@ export interface RuntimePlugin { */ onQuery?: OnQueryCallback; + /** + * Intercepts a procedure invocation. + */ + onProcedure?: OnProcedureCallback; + /** * Intercepts an entity mutation. */ @@ -56,6 +61,42 @@ export function definePlugin(plugin: RuntimePlugin = (ctx: OnProcedureHookContext) => Promise; + +export type OnProcedureHookContext = { + /** + * The procedure name. + */ + name: string; + + /** + * Whether the procedure is a mutation. + */ + mutation: boolean; + + /** + * Procedure invocation input (envelope). + * + * The canonical shape is `{ args?: Record }`. + * When a procedure has required params, `args` is required. + */ + input: unknown; + + /** + * Continues the invocation. The input passed here is forwarded to the next handler. + */ + proceed: (input: unknown) => Promise; + + /** + * The ZenStack client that is invoking the procedure. + */ + client: ClientContract; +}; + +// #endregion + // #region OnQuery hooks type OnQueryCallback = (ctx: OnQueryHookContext) => Promise; diff --git a/packages/orm/src/client/result-processor.ts b/packages/orm/src/client/result-processor.ts index a7870babc..fc8ae1938 100644 --- a/packages/orm/src/client/result-processor.ts +++ b/packages/orm/src/client/result-processor.ts @@ -49,7 +49,7 @@ export class ResultProcessor { // merge delegate descendant fields if (value) { // descendant fields are packed as JSON - const subRow = this.dialect.transformOutput(value, 'Json'); + const subRow = this.dialect.transformOutput(value, 'Json', false); // process the sub-row const subModel = key.slice(DELEGATE_JOINED_FIELD_PREFIX.length) as GetModels; @@ -93,10 +93,10 @@ export class ResultProcessor { private processFieldValue(value: unknown, fieldDef: FieldDef) { const type = fieldDef.type as BuiltinType; if (Array.isArray(value)) { - value.forEach((v, i) => (value[i] = this.dialect.transformOutput(v, type))); + value.forEach((v, i) => (value[i] = this.dialect.transformOutput(v, type, false))); return value; } else { - return this.dialect.transformOutput(value, type); + return this.dialect.transformOutput(value, type, !!fieldDef.array); } } diff --git a/packages/orm/src/utils/type-utils.ts b/packages/orm/src/utils/type-utils.ts index 4c671275c..f1ad3d35c 100644 --- a/packages/orm/src/utils/type-utils.ts +++ b/packages/orm/src/utils/type-utils.ts @@ -31,7 +31,7 @@ export type WrapType = Array extends true ? T | null : T; -type TypeMap = { +export type TypeMap = { String: string; Boolean: boolean; Int: number; @@ -41,6 +41,12 @@ type TypeMap = { DateTime: Date; Bytes: Uint8Array; Json: JsonValue; + Null: null; + Object: Record; + Any: unknown; + Unsupported: unknown; + Void: void; + Undefined: undefined; }; export type MapBaseType = T extends keyof TypeMap ? TypeMap[T] : unknown; diff --git a/packages/orm/test/procedures.test.ts b/packages/orm/test/procedures.test.ts new file mode 100644 index 000000000..6959c8bc3 --- /dev/null +++ b/packages/orm/test/procedures.test.ts @@ -0,0 +1,138 @@ +import SQLite from 'better-sqlite3'; +import { SqliteDialect } from 'kysely'; +import { describe, expect, it } from 'vitest'; + +import { ZenStackClient } from '../src/client/client-impl'; +import { definePlugin } from '../src/client/plugin'; + +const baseSchema = { + provider: { type: 'sqlite' }, + models: {}, + enums: {}, + typeDefs: {}, +} as const; + +describe('ORM procedures', () => { + it('exposes `$procs`', async () => { + const schema: any = { + ...baseSchema, + procedures: { + hello: { params: [], returnType: 'String' }, + }, + }; + + const client: any = new ZenStackClient(schema, { + dialect: new SqliteDialect({ database: new SQLite(':memory:') }), + procedures: { + hello: async () => 'ok', + }, + }); + + expect(typeof client.$procs.hello).toBe('function'); + expect(await client.$procs.hello()).toBe('ok'); + }); + + it('throws config error when procedures are not configured', async () => { + const schema: any = { + ...baseSchema, + procedures: { + hello: { params: [], returnType: 'String' }, + }, + }; + + const client: any = new ZenStackClient(schema, { + dialect: new SqliteDialect({ database: new SQLite(':memory:') }), + } as any); + + await expect(client.$procs.hello()).rejects.toThrow(/not configured/i); + }); + + it('throws error when a procedure handler is missing', async () => { + const schema: any = { + ...baseSchema, + procedures: { + hello: { params: [], returnType: 'String' }, + }, + }; + + const client: any = new ZenStackClient(schema, { + dialect: new SqliteDialect({ database: new SQLite(':memory:') }), + procedures: {}, + } as any); + + await expect(client.$procs.hello()).rejects.toThrow(/does not have a handler configured/i); + }); + + it('validates procedure args against schema', async () => { + const schema: any = { + ...baseSchema, + procedures: { + echoInt: { + params: [{ name: 'n', type: 'Int' }], + returnType: 'Int', + }, + }, + }; + + const client: any = new ZenStackClient(schema, { + dialect: new SqliteDialect({ database: new SQLite(':memory:') }), + procedures: { + echoInt: async ({ args }: any) => args.n, + }, + }); + + await expect(client.$procs.echoInt({ args: { n: '1' } })).rejects.toThrow(/invalid input/i); + }); + + it('runs procedure through onProcedure hooks', async () => { + const schema: any = { + ...baseSchema, + procedures: { + add: { + params: [ + { name: 'a', type: 'Int' }, + { name: 'b', type: 'Int' }, + ], + returnType: 'Int', + }, + }, + }; + + const calls: string[] = []; + + const p1 = definePlugin({ + id: 'p1', + onProcedure: async (ctx) => { + calls.push(`p1:${ctx.name}`); + return ctx.proceed(ctx.input); + }, + }); + + const p2 = definePlugin({ + id: 'p2', + onProcedure: async (ctx) => { + calls.push(`p2:${ctx.name}`); + // mutate args: add +1 to `a` + const nextInput: any = { + ...(ctx.input as any), + args: { + ...((ctx.input as any)?.args ?? {}), + a: Number((ctx.input as any)?.args?.a) + 1, + }, + }; + return ctx.proceed(nextInput); + }, + }); + + const client: any = new ZenStackClient(schema, { + dialect: new SqliteDialect({ database: new SQLite(':memory:') }), + plugins: [p1, p2], + procedures: { + add: async ({ args }: any) => args.a + args.b, + }, + }); + + await expect(client.$procs.add({ args: { a: 1, b: 2 } })).resolves.toBe(4); + expect(calls).toEqual(['p2:add', 'p1:add']); + }); +}); diff --git a/packages/plugins/policy/package.json b/packages/plugins/policy/package.json index 1f75d78cc..0700dddac 100644 --- a/packages/plugins/policy/package.json +++ b/packages/plugins/policy/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/plugin-policy", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack Policy Plugin", "type": "module", "scripts": { diff --git a/packages/plugins/policy/plugin.zmodel b/packages/plugins/policy/plugin.zmodel index 193f5f338..e9f871725 100644 --- a/packages/plugins/policy/plugin.zmodel +++ b/packages/plugins/policy/plugin.zmodel @@ -10,11 +10,10 @@ attribute @@allow(_ operation: String @@@completionHint(["'create'", "'read'", " * Defines an access policy that allows the annotated field to be read or updated. * You can pass a third argument as `true` to make it override the model-level policies. * - * @param operation: comma-separated list of "create", "read", "update", "post-update", "delete". Use "all" to denote all operations. + * @param operation: comma-separated list of "read", "update". Use "all" to denote all operations. * @param condition: a boolean expression that controls if the operation should be allowed. - * @param override: a boolean value that controls if the field-level policy should override the model-level policy. */ -// attribute @allow(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'post-update'", "'delete'", "'all'"]), _ condition: Boolean, _ override: Boolean?) +attribute @allow(_ operation: String @@@completionHint(["'read'", "'update'", "'all'"]), _ condition: Boolean) /** * Defines an access policy that denies a set of operations when the given condition is true. @@ -27,10 +26,10 @@ attribute @@deny(_ operation: String @@@completionHint(["'create'", "'read'", "' /** * Defines an access policy that denies the annotated field to be read or updated. * - * @param operation: comma-separated list of "create", "read", "update", "post-update", "delete". Use "all" to denote all operations. + * @param operation: comma-separated list of "read", "update". Use "all" to denote all operations. * @param condition: a boolean expression that controls if the operation should be denied. */ -// attribute @deny(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean) +attribute @deny(_ operation: String @@@completionHint(["'read'", "'update'", "'all'"]), _ condition: Boolean) /** * Delegates the access control decision to a relation. Only to-one relations are supported. diff --git a/packages/plugins/policy/src/policy-handler.ts b/packages/plugins/policy/src/policy-handler.ts index 3a7726420..54169064e 100644 --- a/packages/plugins/policy/src/policy-handler.ts +++ b/packages/plugins/policy/src/policy-handler.ts @@ -1,6 +1,6 @@ import { invariant } from '@zenstackhq/common-helpers'; -import type { BaseCrudDialect, ClientContract, ProceedKyselyQueryFunction } from '@zenstackhq/orm'; -import { getCrudDialect, QueryUtils, RejectedByPolicyReason, SchemaUtils, type CRUD_EXT } from '@zenstackhq/orm'; +import type { BaseCrudDialect, ClientContract, CRUD_EXT, ProceedKyselyQueryFunction } from '@zenstackhq/orm'; +import { getCrudDialect, QueryUtils, RejectedByPolicyReason, SchemaUtils } from '@zenstackhq/orm'; import { ExpressionUtils, type BuiltinType, @@ -54,6 +54,8 @@ import { falseNode, getTableName, isBeforeInvocation, + isTrueNode, + logicalNot, trueNode, } from './utils'; @@ -61,6 +63,8 @@ export type CrudQueryNode = SelectQueryNode | InsertQueryNode | UpdateQueryNode export type MutationQueryNode = InsertQueryNode | UpdateQueryNode | DeleteQueryNode; +type FieldLevelPolicyOperations = Exclude; + export class PolicyHandler extends OperationNodeTransformer { private readonly dialect: BaseCrudDialect; @@ -73,6 +77,8 @@ export class PolicyHandler extends OperationNodeTransf return this.client.$qb; } + // #region main entry point + async handle(node: RootOperationNode, proceed: ProceedKyselyQueryFunction) { if (!this.isCrudQueryNode(node)) { // non-CRUD queries are not allowed @@ -90,31 +96,22 @@ export class PolicyHandler extends OperationNodeTransf const { mutationModel } = this.getMutationModel(node); + // reject non-existing model this.tryRejectNonexistentModel(mutationModel); - // --- Pre mutation work --- + // #region Pre mutation work + // create if (InsertQueryNode.is(node)) { - // pre-create policy evaluation happens before execution of the query - const isManyToManyJoinTable = this.isManyToManyJoinTable(mutationModel); - let needCheckPreCreate = true; - - // many-to-many join table is not a model so can't have policies on it - if (!isManyToManyJoinTable) { - // check constant policies - const constCondition = this.tryGetConstantPolicy(mutationModel, 'create'); - if (constCondition === true) { - needCheckPreCreate = false; - } else if (constCondition === false) { - throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.NO_ACCESS); - } - } + await this.preCreateCheck(mutationModel, node, proceed); + } - if (needCheckPreCreate) { - await this.enforcePreCreatePolicy(node, mutationModel, isManyToManyJoinTable, proceed); - } + // update + if (UpdateQueryNode.is(node)) { + await this.preUpdateCheck(mutationModel, node, proceed); } + // post-update: load before-update entities if needed const hasPostUpdatePolicies = UpdateQueryNode.is(node) && this.hasPostUpdatePolicies(mutationModel); let beforeUpdateInfo: Awaited> | undefined; @@ -122,11 +119,15 @@ export class PolicyHandler extends OperationNodeTransf beforeUpdateInfo = await this.loadBeforeUpdateEntities(mutationModel, node.where, proceed); } - // proceed with query + // #endregion + + // #region mutation execution const result = await proceed(this.transformNode(node)); - // --- Post mutation work --- + // #endregion + + // #region Post mutation work if (hasPostUpdatePolicies && result.rows.length > 0) { // verify if before-update rows and post-update rows still id-match @@ -204,9 +205,11 @@ export class PolicyHandler extends OperationNodeTransf 'some or all updated rows failed to pass post-update policy check', ); } + + // #endregion } - // --- Read back --- + // #region Read back if (!node.returning || this.onlyReturningId(node)) { // no need to check read back @@ -222,108 +225,136 @@ export class PolicyHandler extends OperationNodeTransf } return readBackResult; } - } - // correction to kysely mutation result may be needed because we might have added - // returning clause to the query and caused changes to the result shape - private postProcessMutationResult(result: QueryResult, node: MutationQueryNode) { - if (node.returning) { - return result; - } else { - return { - ...result, - rows: [], - numAffectedRows: result.numAffectedRows ?? BigInt(result.rows.length), - }; - } + // #endregion } - hasPostUpdatePolicies(model: string) { - const policies = this.getModelPolicies(model, 'post-update'); - return policies.length > 0; - } - - private async loadBeforeUpdateEntities( - model: string, - where: WhereNode | undefined, - proceed: ProceedKyselyQueryFunction, - ) { - const beforeUpdateAccessFields = this.getFieldsAccessForBeforeUpdatePolicies(model); - if (!beforeUpdateAccessFields || beforeUpdateAccessFields.length === 0) { - return undefined; + private async preCreateCheck(mutationModel: string, node: InsertQueryNode, proceed: ProceedKyselyQueryFunction) { + const isManyToManyJoinTable = this.isManyToManyJoinTable(mutationModel); + let needCheckPreCreate = true; + + // many-to-many join table is not a model so can't have policies on it + if (!isManyToManyJoinTable) { + // check constant policies + const constCondition = this.tryGetConstantPolicy(mutationModel, 'create'); + if (constCondition === true) { + needCheckPreCreate = false; + } else if (constCondition === false) { + throw createRejectedByPolicyError(mutationModel, RejectedByPolicyReason.NO_ACCESS); + } } - // combine update's where with policy filter - const policyFilter = this.buildPolicyFilter(model, model, 'update'); - const combinedFilter = where ? conjunction(this.dialect, [where.where, policyFilter]) : policyFilter; - - const query: SelectQueryNode = { - kind: 'SelectQueryNode', - from: FromNode.create([TableNode.create(model)]), - where: WhereNode.create(combinedFilter), - selections: [...beforeUpdateAccessFields.map((f) => SelectionNode.create(ColumnNode.create(f)))], - }; - const result = await proceed(query); - return { fields: beforeUpdateAccessFields, rows: result.rows }; - } - - private getFieldsAccessForBeforeUpdatePolicies(model: string) { - const policies = this.getModelPolicies(model, 'post-update'); - if (policies.length === 0) { - return undefined; + if (needCheckPreCreate) { + await this.enforcePreCreatePolicy(node, mutationModel, isManyToManyJoinTable, proceed); } + } - const fields = new Set(); - const fieldCollector = new (class extends SchemaUtils.ExpressionVisitor { - protected override visitMember(e: MemberExpression): void { - if (isBeforeInvocation(e.receiver)) { - invariant(e.members.length === 1, 'before() can only be followed by a scalar field access'); - fields.add(e.members[0]!); - } - super.visitMember(e); - } - })(); + private async preUpdateCheck(mutationModel: string, node: UpdateQueryNode, proceed: ProceedKyselyQueryFunction) { + // check if any rows will be filtered out by field-level update policies, and reject the whole update if so - for (const policy of policies) { - fieldCollector.visit(policy.condition); - } + const fieldsToUpdate = + node.updates + ?.map((u) => (ColumnNode.is(u.column) ? u.column.column.name : undefined)) + .filter((f): f is string => !!f) ?? []; + const fieldUpdatePolicies = fieldsToUpdate.map((f) => this.buildFieldPolicyFilter(mutationModel, f, 'update')); - if (fields.size === 0) { - return undefined; + // filter combining field-level update policies + const fieldLevelFilter = conjunction(this.dialect, fieldUpdatePolicies); + if (isTrueNode(fieldLevelFilter)) { + return; } - // make sure id fields are included - QueryUtils.requireIdFields(this.client.$schema, model).forEach((f) => fields.add(f)); + // model-level update policy filter + const modelLevelFilter = this.buildPolicyFilter(mutationModel, undefined, 'update'); + + // filter combining model-level update policy and update where + const updateFilter = conjunction(this.dialect, [modelLevelFilter, node.where?.where ?? trueNode(this.dialect)]); + + // build a query to count rows that will be rejected by field-level policies + // `SELECT COALESCE(SUM((not ) as integer), 0) AS $filteredCount WHERE AND ` + const preUpdateCheckQuery = expressionBuilder() + .selectFrom(mutationModel) + .select((eb) => + eb.fn + .coalesce( + eb.fn.sum( + eb.cast(new ExpressionWrapper(logicalNot(this.dialect, fieldLevelFilter)), 'integer'), + ), + eb.lit(0), + ) + .as('$filteredCount'), + ) + .where(() => new ExpressionWrapper(updateFilter)); - return Array.from(fields).sort(); + const preUpdateResult = await proceed(preUpdateCheckQuery.toOperationNode()); + if (preUpdateResult.rows[0].$filteredCount > 0) { + throw createRejectedByPolicyError( + mutationModel, + RejectedByPolicyReason.NO_ACCESS, + 'some rows cannot be updated due to field policies', + ); + } } - // #region overrides + // #endregion + + // #region Transformations protected override transformSelectQuery(node: SelectQueryNode) { if (!node.from) { return super.transformSelectQuery(node); } - let whereNode = this.transformNode(node.where); + // reject non-existing tables + this.tryRejectNonexistingTables(node.from.froms); - // get combined policy filter for all froms, and merge into where clause - const policyFilter = this.createPolicyFilterForFrom(node.from); - if (policyFilter) { - whereNode = WhereNode.create( - whereNode?.where ? conjunction(this.dialect, [whereNode.where, policyFilter]) : policyFilter, - ); - } + let result = super.transformSelectQuery(node); - const baseResult = super.transformSelectQuery({ - ...node, - where: undefined, + const hasFieldLevelPolicies = node.from.froms.some((table) => { + const extractedTable = this.extractTableName(table); + if (extractedTable) { + return this.hasFieldLevelPolicies(extractedTable.model, 'read'); + } else { + return false; + } }); - return { - ...baseResult, - where: whereNode, - }; + if (hasFieldLevelPolicies) { + // when a select query involves field-level policies, we build a nested query selecting all fields guarded with: + // CASE WHEN THEN ELSE NULL END + // model-level policies are also applied at this nested query level + + const updatedFroms: OperationNode[] = []; + for (const table of result.from!.froms) { + const extractedTable = this.extractTableName(table); + if (extractedTable?.model && QueryUtils.getModel(this.client.$schema, extractedTable.model)) { + const { query } = this.createSelectAllFieldsWithPolicies( + extractedTable.model, + extractedTable.alias, + 'read', + ); + updatedFroms.push(query); + } else { + // keep the original from + updatedFroms.push(table); + } + } + result = { ...result, from: FromNode.create(updatedFroms) }; + } else { + // when there's no field-level policies, we merge model-level policy filters into where clause directly + // for generating simpler SQL + + let whereNode = result.where; + const policyFilter = this.createPolicyFilterForFrom(result.from); + if (policyFilter && !isTrueNode(policyFilter)) { + whereNode = WhereNode.create( + whereNode?.where ? conjunction(this.dialect, [whereNode.where, policyFilter]) : policyFilter, + ); + } + result = { ...result, where: whereNode }; + } + + return result; } protected override transformJoin(node: JoinNode) { @@ -333,20 +364,31 @@ export class PolicyHandler extends OperationNodeTransf return super.transformJoin(node); } + // reject non-existing model this.tryRejectNonexistentModel(table.model); - // build a nested query with policy filter applied - const filter = this.buildPolicyFilter(table.model, table.alias, 'read'); + if (!QueryUtils.getModel(this.client.$schema, table.model)) { + // not a defined model, could be m2m join table, keep as is + return super.transformJoin(node); + } - const nestedSelect: SelectQueryNode = { - kind: 'SelectQueryNode', - from: FromNode.create([node.table]), - selections: [SelectionNode.createSelectAll()], - where: WhereNode.create(filter), - }; + const result = super.transformJoin(node); + + const { hasPolicies, query: nestedQuery } = this.createSelectAllFieldsWithPolicies( + table.model, + table.alias, + 'read', + ); + + // join table has no policies, keep it as is + if (!hasPolicies) { + return result; + } + + // otherwise replace it with the nested query guarded with policies return { - ...node, - table: AliasNode.create(ParensNode.create(nestedSelect), IdentifierNode.create(table.alias ?? table.model)), + ...result, + table: nestedQuery, }; } @@ -398,6 +440,9 @@ export class PolicyHandler extends OperationNodeTransf let filter = this.buildPolicyFilter(mutationModel, alias, 'update'); if (node.from) { + // reject non-existing tables + this.tryRejectNonexistingTables(node.from.froms); + // for update with from (join), we need to merge join tables' policy filters to the "where" clause const joinFilter = this.createPolicyFilterForFrom(node.from); if (joinFilter) { @@ -431,6 +476,9 @@ export class PolicyHandler extends OperationNodeTransf let filter = this.buildPolicyFilter(mutationModel, alias, 'delete'); if (node.using) { + // reject non-existing tables + this.tryRejectNonexistingTables(node.using.tables); + // for delete with using (join), we need to merge join tables' policy filters to the "where" clause const joinFilter = this.createPolicyFilterForTables(node.using.tables); if (joinFilter) { @@ -446,6 +494,180 @@ export class PolicyHandler extends OperationNodeTransf // #endregion + // #region post-update + + private async loadBeforeUpdateEntities( + model: string, + where: WhereNode | undefined, + proceed: ProceedKyselyQueryFunction, + ) { + const beforeUpdateAccessFields = this.getFieldsAccessForBeforeUpdatePolicies(model); + if (!beforeUpdateAccessFields || beforeUpdateAccessFields.length === 0) { + return undefined; + } + + // combine update's where with policy filter + const policyFilter = this.buildPolicyFilter(model, model, 'update'); + const combinedFilter = where ? conjunction(this.dialect, [where.where, policyFilter]) : policyFilter; + + const query: SelectQueryNode = { + kind: 'SelectQueryNode', + from: FromNode.create([TableNode.create(model)]), + where: WhereNode.create(combinedFilter), + selections: [...beforeUpdateAccessFields.map((f) => SelectionNode.create(ColumnNode.create(f)))], + }; + const result = await proceed(query); + return { fields: beforeUpdateAccessFields, rows: result.rows }; + } + + private getFieldsAccessForBeforeUpdatePolicies(model: string) { + const policies = this.getModelPolicies(model, 'post-update'); + if (policies.length === 0) { + return undefined; + } + + const fields = new Set(); + const fieldCollector = new (class extends SchemaUtils.ExpressionVisitor { + protected override visitMember(e: MemberExpression): void { + if (isBeforeInvocation(e.receiver)) { + invariant(e.members.length === 1, 'before() can only be followed by a scalar field access'); + fields.add(e.members[0]!); + } + super.visitMember(e); + } + })(); + + for (const policy of policies) { + fieldCollector.visit(policy.condition); + } + + if (fields.size === 0) { + return undefined; + } + + // make sure id fields are included + QueryUtils.requireIdFields(this.client.$schema, model).forEach((f) => fields.add(f)); + + return Array.from(fields).sort(); + } + + private hasPostUpdatePolicies(model: string) { + const policies = this.getModelPolicies(model, 'post-update'); + return policies.length > 0; + } + + // #endregion + + // #region field-level policies + + private createSelectAllFieldsWithPolicies( + model: string, + alias: string | undefined, + operation: FieldLevelPolicyOperations, + ) { + let hasPolicies = false; + const modelDef = QueryUtils.requireModel(this.client.$schema, model); + + let selections: SelectionNode[] = []; + for (const fieldDef of Object.values(modelDef.fields).filter( + // exclude relation/computed/inherited fields + (f) => !f.relation && !f.computed && !f.originModel, + )) { + const { hasPolicies: fieldHasPolicies, selection } = this.createFieldSelectionWithPolicy( + model, + fieldDef.name, + operation, + ); + hasPolicies = hasPolicies || fieldHasPolicies; + selections.push(selection); + } + + if (!hasPolicies) { + // if there're no field-level policies, simplify to select all + selections = [SelectionNode.create(SelectAllNode.create())]; + } + + const modelPolicyFilter = this.buildPolicyFilter(model, alias, operation); + if (!isTrueNode(modelPolicyFilter)) { + hasPolicies = true; + } + + const nestedQuery: SelectQueryNode = { + kind: 'SelectQueryNode', + from: FromNode.create([TableNode.create(model)]), + where: isTrueNode(modelPolicyFilter) ? undefined : WhereNode.create(modelPolicyFilter), + selections, + }; + + return { + hasPolicies, + query: AliasNode.create(ParensNode.create(nestedQuery), IdentifierNode.create(alias ?? model)), + }; + } + + private createFieldSelectionWithPolicy(model: string, field: string, operation: FieldLevelPolicyOperations) { + const filter = this.buildFieldPolicyFilter(model, field, operation); + if (isTrueNode(filter)) { + return { hasPolicies: false, selection: SelectionNode.create(ColumnNode.create(field)) }; + } + const eb = expressionBuilder(); + // CASE WHEN THEN ELSE NULL END + const selection = eb + .case() + .when(new ExpressionWrapper(filter)) + .then(eb.ref(field)) + .else(null) + .end() + .as(field) + .toOperationNode(); + return { hasPolicies: true, selection: SelectionNode.create(selection) }; + } + + private hasFieldLevelPolicies(model: string, operation: FieldLevelPolicyOperations) { + const modelDef = QueryUtils.getModel(this.client.$schema, model); + if (!modelDef) { + return false; + } + return Object.keys(modelDef.fields).some((field) => this.getFieldPolicies(model, field, operation).length > 0); + } + + private buildFieldPolicyFilter(model: string, field: string, operation: FieldLevelPolicyOperations) { + const policies = this.getFieldPolicies(model, field, operation); + + const allows = policies + .filter((policy) => policy.kind === 'allow') + .map((policy) => this.compilePolicyCondition(model, model, operation, policy)); + + const denies = policies + .filter((policy) => policy.kind === 'deny') + .map((policy) => this.compilePolicyCondition(model, model, operation, policy)); + + // 'post-update' is by default allowed, other operations are by default denied + let combinedPolicy: OperationNode; + + if (allows.length === 0) { + // field access is allowed by default + combinedPolicy = trueNode(this.dialect); + } else { + // or(...allows) + combinedPolicy = disjunction(this.dialect, allows); + } + + // and(...!denies) + if (denies.length !== 0) { + const combinedDenies = conjunction( + this.dialect, + denies.map((d) => buildIsFalse(d, this.dialect)), + ); + // or(...allows) && and(...!denies) + combinedPolicy = conjunction(this.dialect, [combinedPolicy, combinedDenies]); + } + + return combinedPolicy; + } + + // #endregion + // #region helpers private onlyReturningId(node: MutationQueryNode) { @@ -876,7 +1098,6 @@ export class PolicyHandler extends OperationNodeTransf const extractResult = this.extractTableName(table); if (extractResult) { const { model, alias } = extractResult; - this.tryRejectNonexistentModel(model); const filter = this.buildPolicyFilter(model, alias, 'read'); return acc ? conjunction(this.dialect, [acc, filter]) : filter; } @@ -929,6 +1150,37 @@ export class PolicyHandler extends OperationNodeTransf return result; } + private getFieldPolicies(model: string, field: string, operation: FieldLevelPolicyOperations) { + const fieldDef = QueryUtils.requireField(this.client.$schema, model, field); + const result: Policy[] = []; + + const extractOperations = (expr: Expression) => { + invariant(ExpressionUtils.isLiteral(expr), 'expecting a literal'); + invariant(typeof expr.value === 'string', 'expecting a string literal'); + return expr.value + .split(',') + .filter((v) => !!v) + .map((v) => v.trim()) as PolicyOperation[]; + }; + + if (fieldDef.attributes) { + result.push( + ...fieldDef.attributes + .filter((attr) => attr.name === '@allow' || attr.name === '@deny') + .map( + (attr) => + ({ + kind: attr.name === '@allow' ? 'allow' : 'deny', + operations: extractOperations(attr.args![0]!.value), + condition: attr.args![1]!.value, + }) as const, + ) + .filter((policy) => policy.operations.includes('all') || policy.operations.includes(operation)), + ); + } + return result; + } + private resolveManyToManyJoinTable(tableName: string) { for (const model of Object.values(this.client.$schema.models)) { for (const field of Object.values(model.fields)) { @@ -1022,5 +1274,28 @@ export class PolicyHandler extends OperationNodeTransf } } + private tryRejectNonexistingTables(tables: readonly OperationNode[]) { + for (const table of tables) { + const extractResult = this.extractTableName(table); + if (extractResult) { + this.tryRejectNonexistentModel(extractResult.model); + } + } + } + + // correction to kysely mutation result may be needed because we might have added + // returning clause to the query and caused changes to the result shape + private postProcessMutationResult(result: QueryResult, node: MutationQueryNode) { + if (node.returning) { + return result; + } else { + return { + ...result, + rows: [], + numAffectedRows: result.numAffectedRows ?? BigInt(result.rows.length), + }; + } + } + // #endregion } diff --git a/packages/plugins/policy/src/utils.ts b/packages/plugins/policy/src/utils.ts index e15e0ccdf..f42370cc8 100644 --- a/packages/plugins/policy/src/utils.ts +++ b/packages/plugins/policy/src/utils.ts @@ -5,6 +5,7 @@ import { AliasNode, AndNode, BinaryOperationNode, + ColumnNode, FunctionNode, OperatorNode, OrNode, @@ -155,6 +156,23 @@ export function getTableName(node: OperationNode | undefined) { return undefined; } +/** + * Gets the column name from a node. + */ +export function getColumnName(node: OperationNode | undefined) { + if (!node) { + return node; + } + if (AliasNode.is(node)) { + return getColumnName(node.node); + } else if (ReferenceNode.is(node)) { + return getColumnName(node.column); + } else if (ColumnNode.is(node)) { + return node.column.name; + } + return undefined; +} + export function isBeforeInvocation(expr: Expression) { return ExpressionUtils.isCall(expr) && expr.function === 'before'; } diff --git a/packages/schema/package.json b/packages/schema/package.json index 800fc42d9..1d6e0146d 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/schema", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack Runtime Schema", "type": "module", "scripts": { diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index 83640c357..40f7d8bd8 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -77,11 +77,12 @@ export type FieldDef = { isDiscriminator?: boolean; }; -export type ProcedureParam = { name: string; type: string; optional?: boolean }; +export type ProcedureParam = { name: string; type: string; array?: boolean; optional?: boolean }; export type ProcedureDef = { - params: [...ProcedureParam[]]; + params: Record; returnType: string; + returnArray?: boolean; mutation?: boolean; }; diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 8baf47d5f..fbdef3447 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack SDK", "type": "module", "scripts": { diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index f68bb0bc6..83e97a279 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -1124,65 +1124,38 @@ export class TsSchemaGenerator { } private createProcedureObject(proc: Procedure) { - const params = ts.factory.createArrayLiteralExpression( + const params = ts.factory.createObjectLiteralExpression( proc.params.map((param) => - ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment('name', ts.factory.createStringLiteral(param.name)), - ...(param.optional - ? [ts.factory.createPropertyAssignment('optional', ts.factory.createTrue())] - : []), - ts.factory.createPropertyAssignment( - 'type', - ts.factory.createStringLiteral(param.type.type ?? param.type.reference!.$refText), - ), - ]), - ), - true, - ); - - const paramsType = ts.factory.createTupleTypeNode([ - ...proc.params.map((param) => - ts.factory.createNamedTupleMember( - undefined, - ts.factory.createIdentifier(param.name), - undefined, - ts.factory.createTypeLiteralNode([ - ts.factory.createPropertySignature( - undefined, - ts.factory.createStringLiteral('name'), - undefined, - ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(param.name)), - ), - ts.factory.createPropertySignature( - undefined, - ts.factory.createStringLiteral('type'), - undefined, - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral(param.type.type ?? param.type.reference!.$refText), - ), - ), + ts.factory.createPropertyAssignment( + param.name, + ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment('name', ts.factory.createStringLiteral(param.name)), ...(param.optional - ? [ - ts.factory.createPropertySignature( - undefined, - ts.factory.createStringLiteral('optional'), - undefined, - ts.factory.createLiteralTypeNode(ts.factory.createTrue()), - ), - ] + ? [ts.factory.createPropertyAssignment('optional', ts.factory.createTrue())] : []), + ...(param.type.array + ? [ts.factory.createPropertyAssignment('array', ts.factory.createTrue())] + : []), + ts.factory.createPropertyAssignment( + 'type', + ts.factory.createStringLiteral(param.type.type ?? param.type.reference!.$refText), + ), ]), ), ), - ]); + true, + ); return ts.factory.createObjectLiteralExpression( [ - ts.factory.createPropertyAssignment('params', ts.factory.createAsExpression(params, paramsType)), + ts.factory.createPropertyAssignment('params', params), ts.factory.createPropertyAssignment( 'returnType', ts.factory.createStringLiteral(proc.returnType.type ?? proc.returnType.reference!.$refText), ), + ...(proc.returnType.array + ? [ts.factory.createPropertyAssignment('returnArray', ts.factory.createTrue())] + : []), ...(proc.mutation ? [ts.factory.createPropertyAssignment('mutation', ts.factory.createTrue())] : []), ], true, @@ -1539,6 +1512,7 @@ export class TsSchemaGenerator { 'FindManyArgs', 'FindUniqueArgs', 'FindFirstArgs', + 'ExistsArgs', 'CreateArgs', 'CreateManyArgs', 'CreateManyAndReturnArgs', diff --git a/packages/server/package.json b/packages/server/package.json index 34399e56a..d39396d56 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack automatic CRUD API handlers and server adapters", "type": "module", "scripts": { diff --git a/packages/server/src/api/common/procedures.ts b/packages/server/src/api/common/procedures.ts new file mode 100644 index 000000000..a0d1e5d55 --- /dev/null +++ b/packages/server/src/api/common/procedures.ts @@ -0,0 +1,137 @@ +import { ORMError } from '@zenstackhq/orm'; +import type { ProcedureDef, ProcedureParam, SchemaDef } from '@zenstackhq/orm/schema'; + +export const PROCEDURE_ROUTE_PREFIXES = '$procs' as const; + +export function getProcedureDef(schema: SchemaDef, proc: string): ProcedureDef | undefined { + const procs = schema.procedures ?? {}; + if (!Object.prototype.hasOwnProperty.call(procs, proc)) { + return undefined; + } + return procs[proc]; +} + +/** + * Maps and validates the incoming procedure payload for server-side routing. + * + * Supported payload formats: + * - **Envelope (preferred)**: `{ args: { ... } }` + * - **Direct object**: `{ ... }` (allowed only when *every* parameter is optional) + * + * The function returns the original `payload` unchanged; it only enforces payload + * *shape* and argument presence/keys so downstream code can safely assume a + * consistent contract. + * + * Validation / branching behavior (mirrors the code below): + * - **Zero-parameter procedures** (`params.length === 0`) + * - `undefined` payload is accepted. + * - Object payloads without an `args` key are treated as “no args” and accepted. + * - Envelope payloads with `args: {}` are accepted. + * - Any other payload (including `args` with keys) is rejected. + * - **All-optional parameter procedures** + * - Payload may be omitted (`undefined`). + * - If payload is an object and has no `args` key, it is treated as the direct + * object form. + * - **Missing payload** (required parameters exist) + * - `undefined` is rejected. + * - **Non-object or array payload** + * - Rejected. + * - **Undefined/invalid `args` (envelope form)** + * - If `args` is missing and not all params are optional: rejected. + * - If `args` exists but is not a non-array object: rejected. + * - **Unknown keys** + * - Any key in the `args` object that is not declared by the procedure is + * rejected (prevents silently ignoring typos). + * - **Missing required params** + * - Any declared non-optional param missing from `args` is rejected. + * + * Rationale for rejecting null/falsey payloads: + * - The checks `!payload` and `!argsPayload` intentionally reject values like + * `null`, `false`, `0`, or `''` instead of treating them as “no args”. This + * keeps the API strictly object-based and yields deterministic, descriptive + * errors rather than surprising coercion. + * + * @throws {Error} "procedure does not accept arguments" + * @throws {Error} "missing procedure arguments" + * @throws {Error} "procedure payload must be an object" + * @throws {Error} "procedure `args` must be an object" + * @throws {Error} "unknown procedure argument: " + * @throws {Error} "missing procedure argument: " + */ +export function mapProcedureArgs(procDef: { params: Record }, payload: unknown): unknown { + const params = Object.values(procDef.params ?? {}); + if (params.length === 0) { + if (typeof payload === 'undefined') { + return undefined; + } + if (payload && typeof payload === 'object' && !Array.isArray(payload)) { + const envelope = payload as Record; + const argsPayload = Object.prototype.hasOwnProperty.call(envelope, 'args') + ? (envelope as any).args + : undefined; + + if (typeof argsPayload === 'undefined') { + return payload; + } + + if (argsPayload && typeof argsPayload === 'object' && !Array.isArray(argsPayload)) { + if (Object.keys(argsPayload as any).length === 0) { + return payload; + } + } + } + throw new Error('procedure does not accept arguments'); + } + + // For procedures where every parameter is optional, allow omitting the payload entirely. + if (typeof payload === 'undefined' && params.every((p) => p.optional)) { + return undefined; + } + + if (typeof payload === 'undefined') { + throw new Error('missing procedure arguments'); + } + + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('procedure payload must be an object'); + } + + const envelope = payload as Record; + const argsPayload = Object.prototype.hasOwnProperty.call(envelope, 'args') ? (envelope as any).args : undefined; + + if (typeof argsPayload === 'undefined') { + if (params.every((p) => p.optional)) { + return payload; + } + throw new Error('missing procedure arguments'); + } + + if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { + throw new Error('procedure `args` must be an object'); + } + + const obj = argsPayload as Record; + + // reject unknown keys to avoid silently ignoring user mistakes + for (const key of Object.keys(obj)) { + if (!params.some((p) => p.name === key)) { + throw new Error(`unknown procedure argument: ${key}`); + } + } + + // ensure required params are present + for (const p of params) { + if (!Object.prototype.hasOwnProperty.call(obj, p.name)) { + if (p.optional) { + continue; + } + throw new Error(`missing procedure argument: ${p.name}`); + } + } + + return payload; +} + +export function isOrmError(err: unknown): err is ORMError { + return err instanceof ORMError; +} diff --git a/packages/server/src/api/common/schemas.ts b/packages/server/src/api/common/schemas.ts new file mode 100644 index 000000000..44e30665c --- /dev/null +++ b/packages/server/src/api/common/schemas.ts @@ -0,0 +1,3 @@ +import z from 'zod'; + +export const loggerSchema = z.union([z.enum(['debug', 'info', 'warn', 'error']).array(), z.function()]); diff --git a/packages/server/src/api/common/utils.ts b/packages/server/src/api/common/utils.ts new file mode 100644 index 000000000..ff42a3f93 --- /dev/null +++ b/packages/server/src/api/common/utils.ts @@ -0,0 +1,56 @@ +import SuperJSON from 'superjson'; + +/** + * Supports the SuperJSON request payload format used by api handlers + * `{ meta: { serialization }, ...json }`. + */ +export async function processSuperJsonRequestPayload(payload: unknown) : Promise<{ result: unknown; error: string | undefined; }> { + if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !('meta' in (payload as any))) { + return { result: payload, error: undefined }; + } + + const { meta, ...rest } = payload as any; + if (meta?.serialization) { + try { + return { + result: SuperJSON.deserialize({ json: rest, meta: meta.serialization }), + error: undefined, + }; + } catch (err) { + return { + result: undefined, + error: `failed to deserialize request payload: ${(err as Error).message}`, + }; + } + } + + // drop meta when no serialization info is present + return { result: rest, error: undefined }; +} + +/** + * Supports the SuperJSON query format used by api handlers: + */ +export function unmarshalQ(value: string, meta: string | undefined) { + let parsedValue: any; + try { + parsedValue = JSON.parse(value); + } catch { + throw new Error('invalid "q" query parameter'); + } + + if (meta) { + let parsedMeta: any; + try { + parsedMeta = JSON.parse(meta); + } catch { + throw new Error('invalid "meta" query parameter'); + } + + if (parsedMeta.serialization) { + return SuperJSON.deserialize({ json: parsedValue, meta: parsedMeta.serialization }); + } + } + + return parsedValue; +} \ No newline at end of file diff --git a/packages/server/src/api/rest/index.ts b/packages/server/src/api/rest/index.ts index ed270cb13..db342ba6e 100644 --- a/packages/server/src/api/rest/index.ts +++ b/packages/server/src/api/rest/index.ts @@ -7,7 +7,11 @@ import tsjapi, { type Linker, type Paginator, type Relator, type Serializer, typ import { match } from 'ts-pattern'; import UrlPattern from 'url-pattern'; import z from 'zod'; +import { fromError } from 'zod-validation-error/v4'; import type { ApiHandler, LogConfig, RequestContext, Response } from '../../types'; +import { getProcedureDef, mapProcedureArgs } from '../common/procedures'; +import { loggerSchema } from '../common/schemas'; +import { processSuperJsonRequestPayload } from '../common/utils'; import { getZodErrorMessage, log, registerCustomSerializers } from '../utils'; /** @@ -51,8 +55,14 @@ export type RestApiHandlerOptions = { */ urlSegmentCharset?: string; + /** + * Mapping from model names to URL segment names. + */ modelNameMapping?: Record; + /** + * Mapping from model names to unique field name to be used as resource's ID. + */ externalIdMapping?: Record; }; @@ -253,6 +263,8 @@ export class RestApiHandler implements Api private externalIdMapping: Record; constructor(private readonly options: RestApiHandlerOptions) { + this.validateOptions(options); + this.idDivider = options.idDivider ?? DEFAULT_ID_DIVIDER; const segmentCharset = options.urlSegmentCharset ?? 'a-zA-Z0-9-_~ %'; @@ -275,6 +287,23 @@ export class RestApiHandler implements Api this.buildSerializers(); } + private validateOptions(options: RestApiHandlerOptions) { + const schema = z.strictObject({ + schema: z.object(), + log: loggerSchema.optional(), + endpoint: z.string().min(1), + pageSize: z.union([z.number().int().positive(), z.literal(Infinity)]).optional(), + idDivider: z.string().min(1).optional(), + urlSegmentCharset: z.string().min(1).optional(), + modelNameMapping: z.record(z.string(), z.string()).optional(), + externalIdMapping: z.record(z.string(), z.string()).optional(), + }); + const parseResult = schema.safeParse(options); + if (!parseResult.success) { + throw new Error(`Invalid options: ${fromError(parseResult.error)}`); + } + } + get schema() { return this.options.schema; } @@ -336,6 +365,11 @@ export class RestApiHandler implements Api } try { + if (path.startsWith('/$procs/')) { + const proc = path.split('/')[2]; + return await this.processProcedureRequest({ client, method, proc, query, requestBody }); + } + switch (method) { case 'GET': { let match = this.matchUrlPattern(path, UrlPatterns.SINGLE); @@ -473,6 +507,91 @@ export class RestApiHandler implements Api return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error'); } + private async processProcedureRequest({ + client, + method, + proc, + query, + requestBody, + }: { + client: ClientContract; + method: string; + proc?: string; + query?: Record; + requestBody?: unknown; + }): Promise { + if (!proc) { + return this.makeProcBadInputErrorResponse('missing procedure name'); + } + + const procDef = getProcedureDef(this.schema, proc); + if (!procDef) { + return this.makeProcBadInputErrorResponse(`unknown procedure: ${proc}`); + } + + const isMutation = !!procDef.mutation; + if (isMutation) { + if (method !== 'POST') { + return this.makeProcBadInputErrorResponse('invalid request method, only POST is supported'); + } + } else { + if (method !== 'GET') { + return this.makeProcBadInputErrorResponse('invalid request method, only GET is supported'); + } + } + + const argsPayload = method === 'POST' ? requestBody : query; + + // support SuperJSON request payload format + const { result: processedArgsPayload, error } = await processSuperJsonRequestPayload(argsPayload); + if (error) { + return this.makeProcBadInputErrorResponse(error); + } + + let procInput: unknown; + try { + procInput = mapProcedureArgs(procDef, processedArgsPayload); + } catch (err) { + return this.makeProcBadInputErrorResponse( + err instanceof Error ? err.message : 'invalid procedure arguments', + ); + } + + try { + log(this.log, 'debug', () => `handling "$procs.${proc}" request`); + + const clientResult = await (client as any).$procs?.[proc](procInput); + const toSerialize = this.toPlainObject(clientResult); + + const { json, meta } = SuperJSON.serialize(toSerialize); + const responseBody: any = { data: json }; + if (meta) { + responseBody.meta = { serialization: meta }; + } + + return { status: 200, body: responseBody }; + } catch (err) { + log(this.log, 'error', `error occurred when handling "$procs.${proc}" request`, err); + if (err instanceof ORMError) { + throw err; // top-level handler will take care of it + } + return this.makeProcGenericErrorResponse(err); + } + } + + private makeProcBadInputErrorResponse(message: string): Response { + const resp = this.makeError('invalidPayload', message, 400); + log(this.log, 'debug', () => `sending error response: ${JSON.stringify(resp)}`); + return resp; + } + + private makeProcGenericErrorResponse(err: unknown): Response { + const message = err instanceof Error ? err.message : 'unknown error'; + const resp = this.makeError('unknownError', message, 500); + log(this.log, 'debug', () => `sending error response: ${JSON.stringify(resp)}`); + return resp; + } + private async processSingleRead( client: ClientContract, type: string, diff --git a/packages/server/src/api/rpc/index.ts b/packages/server/src/api/rpc/index.ts index e821366fd..6f572e82d 100644 --- a/packages/server/src/api/rpc/index.ts +++ b/packages/server/src/api/rpc/index.ts @@ -3,7 +3,12 @@ import { ORMError, ORMErrorReason, type ClientContract } from '@zenstackhq/orm'; import type { SchemaDef } from '@zenstackhq/orm/schema'; import SuperJSON from 'superjson'; import { match } from 'ts-pattern'; +import z from 'zod'; +import { fromError } from 'zod-validation-error/v4'; import type { ApiHandler, LogConfig, RequestContext, Response } from '../../types'; +import { getProcedureDef, mapProcedureArgs, PROCEDURE_ROUTE_PREFIXES } from '../common/procedures'; +import { loggerSchema } from '../common/schemas'; +import { processSuperJsonRequestPayload, unmarshalQ } from '../common/utils'; import { log, registerCustomSerializers } from '../utils'; registerCustomSerializers(); @@ -27,7 +32,17 @@ export type RPCApiHandlerOptions = { * RPC style API request handler that mirrors the ZenStackClient API */ export class RPCApiHandler implements ApiHandler { - constructor(private readonly options: RPCApiHandlerOptions) {} + constructor(private readonly options: RPCApiHandlerOptions) { + this.validateOptions(options); + } + + private validateOptions(options: RPCApiHandlerOptions) { + const schema = z.strictObject({ schema: z.object(), log: loggerSchema.optional() }); + const parseResult = schema.safeParse(options); + if (!parseResult.success) { + throw new Error(`Invalid options: ${fromError(parseResult.error)}`); + } + } get schema(): Schema { return this.options.schema; @@ -46,6 +61,16 @@ export class RPCApiHandler implements ApiH return this.makeBadInputErrorResponse('invalid request path'); } + if (model === PROCEDURE_ROUTE_PREFIXES) { + return this.handleProcedureRequest({ + client, + method: method.toUpperCase(), + proc: op, + query, + requestBody, + }); + } + model = lowerCaseFirst(model); method = method.toUpperCase(); let args: unknown; @@ -73,13 +98,12 @@ export class RPCApiHandler implements ApiH case 'aggregate': case 'groupBy': case 'count': + case 'exists': if (method !== 'GET') { return this.makeBadInputErrorResponse('invalid request method, only GET is supported'); } try { - args = query?.['q'] - ? this.unmarshalQ(query['q'] as string, query['meta'] as string | undefined) - : {}; + args = query?.['q'] ? unmarshalQ(query['q'] as string, query['meta'] as string | undefined) : {}; } catch { return this.makeBadInputErrorResponse('invalid "q" query parameter'); } @@ -104,9 +128,7 @@ export class RPCApiHandler implements ApiH return this.makeBadInputErrorResponse('invalid request method, only DELETE is supported'); } try { - args = query?.['q'] - ? this.unmarshalQ(query['q'] as string, query['meta'] as string | undefined) - : {}; + args = query?.['q'] ? unmarshalQ(query['q'] as string, query['meta'] as string | undefined) : {}; } catch (err) { return this.makeBadInputErrorResponse( err instanceof Error ? err.message : 'invalid "q" query parameter', @@ -163,6 +185,92 @@ export class RPCApiHandler implements ApiH } } + private async handleProcedureRequest({ + client, + method, + proc, + query, + requestBody, + }: { + client: ClientContract; + method: string; + proc?: string; + query?: Record; + requestBody?: unknown; + }): Promise { + if (!proc) { + return this.makeBadInputErrorResponse('missing procedure name'); + } + + const procDef = getProcedureDef(this.options.schema, proc); + if (!procDef) { + return this.makeBadInputErrorResponse(`unknown procedure: ${proc}`); + } + + const isMutation = !!procDef.mutation; + + if (isMutation) { + if (method !== 'POST') { + return this.makeBadInputErrorResponse('invalid request method, only POST is supported'); + } + } else { + if (method !== 'GET') { + return this.makeBadInputErrorResponse('invalid request method, only GET is supported'); + } + } + + let argsPayload = method === 'POST' ? requestBody : undefined; + if (method === 'GET') { + try { + argsPayload = query?.['q'] + ? unmarshalQ(query['q'] as string, query['meta'] as string | undefined) + : undefined; + } catch (err) { + return this.makeBadInputErrorResponse( + err instanceof Error ? err.message : 'invalid "q" query parameter', + ); + } + } + + const { result: processedArgsPayload, error } = await processSuperJsonRequestPayload(argsPayload); + if (error) { + return this.makeBadInputErrorResponse(error); + } + + let procInput: unknown; + try { + procInput = mapProcedureArgs(procDef, processedArgsPayload); + } catch (err) { + return this.makeBadInputErrorResponse(err instanceof Error ? err.message : 'invalid procedure arguments'); + } + + try { + log(this.options.log, 'debug', () => `handling "$procs.${proc}" request`); + + const clientResult = await (client as any).$procs?.[proc](procInput); + + const { json, meta } = SuperJSON.serialize(clientResult); + const responseBody: any = { data: json }; + if (meta) { + responseBody.meta = { serialization: meta }; + } + + const response = { status: 200, body: responseBody }; + log( + this.options.log, + 'debug', + () => `sending response for "$procs.${proc}" request: ${safeJSONStringify(response)}`, + ); + return response; + } catch (err) { + log(this.options.log, 'error', `error occurred when handling "$procs.${proc}" request`, err); + if (err instanceof ORMError) { + return this.makeORMErrorResponse(err); + } + return this.makeGenericErrorResponse(err); + } + } + private isValidModel(client: ClientContract, model: string) { return Object.keys(client.$schema.models).some((m) => lowerCaseFirst(m) === lowerCaseFirst(model)); } @@ -206,8 +314,8 @@ export class RPCApiHandler implements ApiH .with(ORMErrorReason.REJECTED_BY_POLICY, () => { status = 403; error.rejectedByPolicy = true; - error.rejectReason = err.rejectedByPolicyReason; error.model = err.model; + error.rejectReason = err.rejectedByPolicyReason; }) .with(ORMErrorReason.DB_QUERY_ERROR, () => { status = 400; @@ -235,28 +343,4 @@ export class RPCApiHandler implements ApiH } return { result: args, error: undefined }; } - - private unmarshalQ(value: string, meta: string | undefined) { - let parsedValue: any; - try { - parsedValue = JSON.parse(value); - } catch { - throw new Error('invalid "q" query parameter'); - } - - if (meta) { - let parsedMeta: any; - try { - parsedMeta = JSON.parse(meta); - } catch { - throw new Error('invalid "meta" query parameter'); - } - - if (parsedMeta.serialization) { - return SuperJSON.deserialize({ json: parsedValue, meta: parsedMeta.serialization }); - } - } - - return parsedValue; - } } diff --git a/packages/server/test/api/options-validation.test.ts b/packages/server/test/api/options-validation.test.ts new file mode 100644 index 000000000..53f7f3680 --- /dev/null +++ b/packages/server/test/api/options-validation.test.ts @@ -0,0 +1,628 @@ +import { ClientContract } from '@zenstackhq/orm'; +import { SchemaDef } from '@zenstackhq/orm/schema'; +import { createTestClient } from '@zenstackhq/testtools'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { RestApiHandler } from '../../src/api/rest'; +import { RPCApiHandler } from '../../src/api/rpc'; + +describe('API Handler Options Validation', () => { + let client: ClientContract; + + const testSchema = ` + model User { + id String @id @default(cuid()) + email String @unique + name String + } + `; + + beforeEach(async () => { + client = await createTestClient(testSchema); + }); + + describe('RestApiHandler Options Validation', () => { + it('should accept valid options', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + }); + }).not.toThrow(); + }); + + it('should accept valid options with all optional fields', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + log: ['debug', 'info', 'warn', 'error'], + pageSize: 50, + idDivider: '-', + urlSegmentCharset: 'a-zA-Z0-9-_~', + modelNameMapping: { User: 'users' }, + }); + }).not.toThrow(); + }); + + it('should accept custom log function', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + log: (level: string, message: string) => { + console.log(`[${level}] ${message}`); + }, + }); + }).not.toThrow(); + }); + + it('should throw error when schema is missing', () => { + expect(() => { + new RestApiHandler({ + endpoint: 'http://localhost/api', + } as any); + }).toThrow('Invalid options'); + }); + + it('should throw error when endpoint is missing', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + } as any); + }).toThrow('Invalid options'); + }); + + it('should throw error when endpoint is empty string', () => { + // Note: Zod z.string() validation allows empty strings + // The endpoint validation doesn't enforce non-empty string + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: '', + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when endpoint is not a string', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 123, + } as any); + }).toThrow('Invalid options'); + }); + + it('should throw error when pageSize is not a number', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: 'invalid' as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when pageSize is zero', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: 0, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when pageSize is negative', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: -10, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when idDivider is empty string', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + idDivider: '', + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when idDivider is not a string', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + idDivider: 123 as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when urlSegmentCharset is empty string', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + urlSegmentCharset: '', + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when urlSegmentCharset is not a string', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + urlSegmentCharset: [] as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when modelNameMapping is not an object', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + modelNameMapping: 'invalid' as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when modelNameMapping values are not strings', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + modelNameMapping: { User: 123 } as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when externalIdMapping is not an object', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + externalIdMapping: 'invalid' as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when externalIdMapping values are not strings', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + externalIdMapping: { User: 123 } as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when log is invalid type', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + log: 'invalid' as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when log array contains invalid values', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + log: ['debug', 'invalid'] as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when schema is not an object', () => { + expect(() => { + new RestApiHandler({ + schema: 'invalid' as any, + endpoint: 'http://localhost/api', + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when schema is null', () => { + expect(() => { + new RestApiHandler({ + schema: null as any, + endpoint: 'http://localhost/api', + }); + }).toThrow('Invalid options'); + }); + + it('should accept valid pageSize of 1', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: 1, + }); + }).not.toThrow(); + }); + + it('should accept large pageSize', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: 10000, + }); + }).not.toThrow(); + }); + + it('should accept Infinity as pageSize to disable pagination', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: Infinity, + }); + }).not.toThrow(); + }); + + it('should throw error when pageSize is a decimal', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: 10.5, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when pageSize is NaN', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: NaN, + }); + }).toThrow('Invalid options'); + }); + + it('should accept single character idDivider', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + idDivider: '|', + }); + }).not.toThrow(); + }); + + it('should accept multi-character idDivider', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + idDivider: '---', + }); + }).not.toThrow(); + }); + }); + + describe('RPCApiHandler Options Validation', () => { + it('should accept valid options', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + }); + }).not.toThrow(); + }); + + it('should accept valid options with log array', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: ['debug', 'info', 'warn', 'error'], + }); + }).not.toThrow(); + }); + + it('should accept valid options with log function', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: (level: string, message: string) => { + console.log(`[${level}] ${message}`); + }, + }); + }).not.toThrow(); + }); + + it('should throw error when schema is missing', () => { + expect(() => { + new RPCApiHandler({} as any); + }).toThrow('Invalid options'); + }); + + it('should throw error when schema is not an object', () => { + expect(() => { + new RPCApiHandler({ + schema: 'invalid' as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when schema is null', () => { + expect(() => { + new RPCApiHandler({ + schema: null as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when schema is undefined', () => { + expect(() => { + new RPCApiHandler({ + schema: undefined as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when log is invalid type', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: 'invalid' as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when log array contains invalid values', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: ['debug', 'invalid'] as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when log is a number', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: 123 as any, + }); + }).toThrow('Invalid options'); + }); + + it('should throw error when log is an object', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: {} as any, + }); + }).toThrow('Invalid options'); + }); + + it('should accept empty log array', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: [], + }); + }).not.toThrow(); + }); + + it('should accept single log level', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: ['error'], + }); + }).not.toThrow(); + }); + + it('should throw error with extra unknown options', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + unknownOption: 'value', + } as any); + }).toThrow('Invalid options'); // z.strictObject() rejects extra properties + }); + }); + + describe('strictObject validation - extra properties', () => { + it('RestApiHandler should reject extra unknown properties', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + extraProperty: 'should-fail', + } as any); + }).toThrow('Invalid options'); + }); + + it('RPCApiHandler should reject extra unknown properties', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + extraProperty: 'should-fail', + } as any); + }).toThrow('Invalid options'); + }); + + it('RestApiHandler should reject multiple extra unknown properties', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + extra1: 'value1', + extra2: 'value2', + } as any); + }).toThrow('Invalid options'); + }); + + it('RPCApiHandler should reject multiple extra unknown properties', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + extra1: 'value1', + extra2: 'value2', + } as any); + }).toThrow('Invalid options'); + }); + }); + + describe('Edge Cases and Type Safety', () => { + it('RestApiHandler should handle undefined optional fields gracefully', () => { + const handler = new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + log: undefined, + pageSize: undefined, + idDivider: undefined, + }); + expect(handler).toBeDefined(); + }); + + it('RPCApiHandler should handle undefined optional fields gracefully', () => { + const handler = new RPCApiHandler({ + schema: client.$schema, + log: undefined, + }); + expect(handler).toBeDefined(); + }); + + it('RestApiHandler should expose schema property', () => { + const handler = new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + }); + expect(handler.schema).toBe(client.$schema); + }); + + it('RPCApiHandler should expose schema property', () => { + const handler = new RPCApiHandler({ + schema: client.$schema, + }); + expect(handler.schema).toBe(client.$schema); + }); + + it('RestApiHandler should expose log property', () => { + const logConfig = ['debug', 'error'] as const; + const handler = new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + log: logConfig, + }); + expect(handler.log).toBe(logConfig); + }); + + it('RPCApiHandler should expose log property', () => { + const logConfig = ['debug', 'error'] as const; + const handler = new RPCApiHandler({ + schema: client.$schema, + log: logConfig, + }); + expect(handler.log).toBe(logConfig); + }); + + it('RestApiHandler should handle empty modelNameMapping', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + modelNameMapping: {}, + }); + }).not.toThrow(); + }); + + it('RestApiHandler should handle empty externalIdMapping', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + externalIdMapping: {}, + }); + }).not.toThrow(); + }); + }); + + describe('Real-world Scenarios', () => { + it('RestApiHandler with production-like configuration', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'https://api.example.com/v1', + log: (level, message) => { + if (level === 'error') { + console.error(message); + } + }, + pageSize: 100, + idDivider: '_', + modelNameMapping: { + User: 'users', + }, + }); + }).not.toThrow(); + }); + + it('RPCApiHandler with production-like configuration', () => { + expect(() => { + new RPCApiHandler({ + schema: client.$schema, + log: (level, message) => { + if (level === 'error') { + console.error(message); + } + }, + }); + }).not.toThrow(); + }); + + it('RestApiHandler with disabled pagination (Infinity pageSize)', () => { + expect(() => { + new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: Infinity, + }); + }).not.toThrow(); + }); + }); + + describe('Schema validation', () => { + it('RestApiHandler should validate schema structure', () => { + const validSchema = client.$schema; + expect(() => { + new RestApiHandler({ + schema: validSchema, + endpoint: 'http://localhost/api', + }); + }).not.toThrow(); + }); + + it('RPCApiHandler should validate schema structure', () => { + const validSchema = client.$schema; + expect(() => { + new RPCApiHandler({ + schema: validSchema, + }); + }).not.toThrow(); + }); + + it('RestApiHandler should handle empty schema object but will error when building type map', () => { + // Empty schema passes Zod validation (z.object()) but fails when building type map + expect(() => { + new RestApiHandler({ + schema: {} as any, + endpoint: 'http://localhost/api', + }); + }).toThrow(); // Throws when trying to build type map from empty schema + }); + }); +}); diff --git a/packages/server/test/api/procedures.e2e.test.ts b/packages/server/test/api/procedures.e2e.test.ts new file mode 100644 index 000000000..60c6f06b9 --- /dev/null +++ b/packages/server/test/api/procedures.e2e.test.ts @@ -0,0 +1,86 @@ +import type { ClientContract } from '@zenstackhq/orm'; +import type { SchemaDef } from '@zenstackhq/orm/schema'; +import { createTestClient } from '@zenstackhq/testtools'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { RestApiHandler } from '../../src/api/rest'; + +describe('Procedures E2E', () => { + let client: ClientContract; + let api: RestApiHandler; + + const schema = ` +datasource db { + provider = 'sqlite' + url = 'file:./test.db' +} + +model User { + id Int @id @default(autoincrement()) + email String @unique +} + +procedure greet(name: String?): String +mutation procedure createTwoAndFail(email1: String, email2: String): Int +`; + + beforeEach(async () => { + client = await createTestClient( + schema, + { + procedures: { + greet: async ({ args }: any) => { + const name = args?.name as string | undefined; + return `hello ${name ?? 'world'}`; + }, + createTwoAndFail: async ({ client, args }: any) => { + const email1 = args.email1 as string; + const email2 = args.email2 as string; + await client.user.create({ data: { email: email1 } }); + await client.user.create({ data: { email: email2 } }); + throw new Error('boom'); + }, + }, + } as any + ); + + api = new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: 5, + }); + }); + + afterEach(async () => { + await client?.$disconnect(); + }); + + it('supports $procs routes', async () => { + const r = await api.handleRequest({ + client, + method: 'get', + path: '/$procs/greet', + query: { args: { name: 'alice' } } as any, + }); + expect(r.status).toBe(200); + expect(r.body).toEqual({ data: 'hello alice' }); + }); + + it('returns 422 for invalid input', async () => { + const r = await api.handleRequest({ + client, + method: 'get', + path: '/$procs/greet', + query: { args: { name: 123 } } as any, + }); + + expect(r.status).toBe(422); + expect(r.body).toMatchObject({ + errors: [ + { + status: 422, + code: 'validation-error', + }, + ], + }); + }); +}); diff --git a/packages/server/test/api/rest.test.ts b/packages/server/test/api/rest.test.ts index b40ff6044..124967edd 100644 --- a/packages/server/test/api/rest.test.ts +++ b/packages/server/test/api/rest.test.ts @@ -3163,4 +3163,301 @@ describe('REST server tests', () => { }); }); }); + + describe('REST server tests - procedures', () => { + const schema = ` +model User { + id String @id @default(cuid()) + email String @unique +} + +enum Role { + ADMIN + USER +} + +type Overview { + total Int +} + +procedure echoDecimal(x: Decimal): Decimal +procedure greet(name: String?): String +procedure echoInt(x: Int): Int +procedure opt2(a: Int?, b: Int?): Int +procedure sumIds(ids: Int[]): Int +procedure echoRole(r: Role): Role +procedure echoOverview(o: Overview): Overview + +mutation procedure sum(a: Int, b: Int): Int +`; + + beforeEach(async () => { + interface ProcCtx { + client: ClientContract; + args: TArgs; + } + + interface ProcCtxOptionalArgs { + client: ClientContract; + args?: TArgs; + } + + type Role = 'ADMIN' | 'USER'; + + interface Overview { + total: number; + } + + interface EchoDecimalArgs { + x: Decimal; + } + + interface GreetArgs { + name?: string | null; + } + + interface EchoIntArgs { + x: number; + } + + interface Opt2Args { + a?: number | null; + b?: number | null; + } + + interface SumIdsArgs { + ids: number[]; + } + + interface EchoRoleArgs { + r: Role; + } + + interface EchoOverviewArgs { + o: Overview; + } + + interface SumArgs { + a: number; + b: number; + } + + interface Procedures { + echoDecimal: (ctx: ProcCtx) => Promise; + greet: (ctx: ProcCtxOptionalArgs) => Promise; + echoInt: (ctx: ProcCtx) => Promise; + opt2: (ctx: ProcCtxOptionalArgs) => Promise; + sumIds: (ctx: ProcCtx) => Promise; + echoRole: (ctx: ProcCtx) => Promise; + echoOverview: (ctx: ProcCtx) => Promise; + sum: (ctx: ProcCtx) => Promise; + } + + client = await createTestClient(schema as unknown as SchemaDef, { + procedures: { + echoDecimal: async ({ args }: ProcCtx) => args.x, + greet: async ({ args }: ProcCtxOptionalArgs) => { + const name = args?.name as string | undefined; + return `hi ${name ?? 'anon'}`; + }, + echoInt: async ({ args }: ProcCtx) => args.x, + opt2: async ({ args }: ProcCtxOptionalArgs) => { + const a = args?.a as number | undefined; + const b = args?.b as number | undefined; + return (a ?? 0) + (b ?? 0); + }, + sumIds: async ({ args }: ProcCtx) => (args.ids as number[]).reduce((acc, x) => acc + x, 0), + echoRole: async ({ args }: ProcCtx) => args.r, + echoOverview: async ({ args }: ProcCtx) => args.o, + sum: async ({ args }: ProcCtx) => args.a + args.b, + } as Procedures, + }); + + const _handler = new RestApiHandler({ + schema: client.$schema, + endpoint: 'http://localhost/api', + pageSize: 5, + }); + + handler = (args) => _handler.handleRequest({ ...args, url: new URL(`http://localhost/${args.path}`) }); + }); + + it('supports GET query procedures with q/meta (SuperJSON)', async () => { + const { json, meta } = SuperJSON.serialize({ args: { x: new Decimal('1.23') } }); + const r = await handler({ + method: 'get', + path: '/$procs/echoDecimal', + query: { ...json as object, meta: { serialization: meta } } as any, + client, + }); + + expect(r.status).toBe(200); + expect(r.body).toMatchObject({ data: '1.23' }); + }); + + it('supports GET procedures without args when param is optional', async () => { + const r = await handler({ + method: 'get', + path: '/$procs/greet', + query: {}, + client, + }); + + expect(r.status).toBe(200); + expect(r.body).toMatchObject({ data: 'hi anon' }); + }); + + it('errors for missing required single-param arg', async () => { + const r = await handler({ + method: 'get', + path: '/$procs/echoInt', + query: {}, + client, + }); + + expect(r.status).toBe(400); + expect(r.body).toMatchObject({ + errors: [ + { + status: 400, + code: 'invalid-payload', + detail: 'missing procedure arguments', + }, + ], + }); + }); + + it('supports GET procedures without args when all params are optional', async () => { + const r = await handler({ + method: 'get', + path: '/$procs/opt2', + query: {}, + client, + }); + + expect(r.status).toBe(200); + expect(r.body).toMatchObject({ data: 0 }); + }); + + it('supports array-typed single param via envelope args', async () => { + const r = await handler({ + method: 'get', + path: '/$procs/sumIds', + query: { args: { ids: [1, 2, 3] } } as any, + client, + }); + + expect(r.status).toBe(200); + expect(r.body).toMatchObject({ data: 6 }); + }); + + it('supports enum-typed params with validation', async () => { + const r = await handler({ + method: 'get', + path: '/$procs/echoRole', + query: { args: { r: 'ADMIN' } } as any, + client, + }); + + expect(r.status).toBe(200); + expect(r.body).toMatchObject({ data: 'ADMIN' }); + }); + + it('supports typedef params (object payload)', async () => { + const r = await handler({ + method: 'get', + path: '/$procs/echoOverview', + query: { args: { o: { total: 123 } } } as any, + client, + }); + + expect(r.status).toBe(200); + expect(r.body).toMatchObject({ data: { total: 123 } }); + }); + + it('errors for wrong type input', async () => { + const r = await handler({ + method: 'get', + path: '/$procs/echoInt', + query: { args: { x: 'not-an-int' } } as any, + client, + }); + + expect(r.status).toBe(422); + expect(r.body).toMatchObject({ + errors: [ + { + status: 422, + code: 'validation-error', + }, + ], + }); + expect(r.body.errors?.[0]?.detail).toMatch(/invalid input/i); + }); + + it('supports POST mutation procedures with args passed via q/meta', async () => { + const { json, meta } = SuperJSON.serialize({ args: { a: 1, b: 2 } }); + const r = await handler({ + method: 'post', + path: '/$procs/sum', + requestBody: { ...json as object, meta: { serialization: meta } } as any, + client, + }); + + expect(r.status).toBe(200); + expect(r.body).toMatchObject({ data: 3 }); + }); + + it('errors for invalid `args` payload type', async () => { + const r = await handler({ + method: 'post', + path: '/$procs/sum', + requestBody: { args: [1, 2, 3] } as any, + client, + }); + + expect(r.status).toBe(400); + expect(r.body).toMatchObject({ + errors: [ + { + status: 400, + code: 'invalid-payload', + }, + ], + }); + expect(r.body.errors?.[0]?.detail).toMatch(/args/i); + }); + + it('errors for unknown argument keys (object mapping)', async () => { + const r = await handler({ + method: 'post', + path: '/$procs/sum', + requestBody: { args: { a: 1, b: 2, c: 3 } } as any, + client, + }); + + expect(r.status).toBe(400); + expect(r.body).toMatchObject({ + errors: [ + { + status: 400, + code: 'invalid-payload', + }, + ], + }); + expect(r.body.errors?.[0]?.detail).toMatch(/unknown procedure argument/i); + }); + + it('supports /$procs path', async () => { + const r = await handler({ + method: 'post', + path: '/$procs/sum', + requestBody: { args: { a: 1, b: 2 } } as any, + client, + }); + + expect(r.status).toBe(200); + expect(r.body).toMatchObject({ data: 3 }); + }); + }); }); diff --git a/packages/server/test/api/rpc.test.ts b/packages/server/test/api/rpc.test.ts index 4329e857e..9493ed2a3 100644 --- a/packages/server/test/api/rpc.test.ts +++ b/packages/server/test/api/rpc.test.ts @@ -27,6 +27,15 @@ describe('RPC API Handler Tests', () => { expect(r.status).toBe(200); expect(r.data).toHaveLength(0); + r = await handleRequest({ + method: 'get', + path: '/user/exists', + query: { q: JSON.stringify({ where: { id: 'user1' }})}, + client: rawClient, + }); + expect(r.status).toBe(200); + expect(r.data).toBe(false); + r = await handleRequest({ method: 'post', path: '/user/create', @@ -57,6 +66,15 @@ describe('RPC API Handler Tests', () => { }), ); + r = await handleRequest({ + method: 'get', + path: '/user/exists', + query: { q: JSON.stringify({ where: { id: 'user1' }})}, + client: rawClient, + }); + expect(r.status).toBe(200); + expect(r.data).toBe(true); + r = await handleRequest({ method: 'get', path: '/post/findMany', @@ -125,6 +143,217 @@ describe('RPC API Handler Tests', () => { expect(r.data.count).toBe(1); }); + it('procedures', async () => { + const procSchema = ` +model User { + id String @id @default(cuid()) + email String @unique @email + + @@allow('all', true) +} + +procedure echo(input: String): String +mutation procedure createUser(email: String): User +procedure getFalse(): Boolean +procedure getUndefined(): Undefined +`; + + const procClient = await createPolicyTestClient(procSchema, { + procedures: { + echo: async ({ args }: any) => args.input, + createUser: async ({ client, args }: any) => { + return client.user.create({ data: { email: args.email } }); + }, + getFalse: async () => false, + getUndefined: async () => undefined, + }, + }); + + const handler = new RPCApiHandler({ schema: procClient.$schema }); + const handleProcRequest = async (args: any) => { + const r = await handler.handleRequest({ + ...args, + client: procClient, + url: new URL(`http://localhost/${args.path}`), + }); + return { + status: r.status, + body: r.body as any, + data: (r.body as any).data, + error: (r.body as any).error, + meta: (r.body as any).meta, + }; + }; + + // query procedure: GET only, args via q + let r = await handleProcRequest({ + method: 'get', + path: '/$procs/echo', + query: { q: JSON.stringify({ args: { input: 'hello' } }) }, + }); + expect(r.status).toBe(200); + expect(r.data).toBe('hello'); + + r = await handleProcRequest({ + method: 'post', + path: '/$procs/echo', + requestBody: { args: { input: 'hello' } }, + }); + expect(r.status).toBe(400); + expect(r.error?.message).toMatch(/only GET is supported/i); + + // mutation procedure: POST only, args via body + r = await handleProcRequest({ + method: 'post', + path: '/$procs/createUser', + requestBody: { args: { email: 'user1@abc.com' } }, + }); + expect(r.status).toBe(200); + expect(r.data).toEqual(expect.objectContaining({ email: 'user1@abc.com' })); + + r = await handleProcRequest({ + method: 'get', + path: '/$procs/createUser', + query: { q: JSON.stringify({ args: { email: 'user2@abc.com' } }) }, + }); + expect(r.status).toBe(400); + expect(r.error?.message).toMatch(/only POST is supported/i); + + // falsy/undefined return serialization + r = await handleProcRequest({ method: 'get', path: '/$procs/getFalse' }); + expect(r.status).toBe(200); + expect(r.data).toBe(false); + + r = await handleProcRequest({ method: 'get', path: '/$procs/getUndefined' }); + expect(r.status).toBe(200); + expect(r.data).toBeNull(); + expect(r.meta?.serialization).toBeTruthy(); + }); + + it('procedures - edge cases', async () => { + const procSchema = ` +model User { + id String @id @default(cuid()) + email String @unique @email +} + +enum Role { + ADMIN + USER +} + +type Overview { + total Int +} + +procedure echoInt(x: Int): Int +procedure opt2(a: Int?, b: Int?): Int +procedure sum3(a: Int, b: Int, c: Int): Int +procedure sumIds(ids: Int[]): Int +procedure echoRole(r: Role): Role +procedure echoOverview(o: Overview): Overview +`; + + const procClient = await createPolicyTestClient(procSchema, { + procedures: { + echoInt: async ({ args }: any) => args.x, + opt2: async ({ args }: any) => { + const a = args?.a as number | undefined; + const b = args?.b as number | undefined; + return (a ?? 0) + (b ?? 0); + }, + sum3: async ({ args }: any) => args.a + args.b + args.c, + sumIds: async ({ args }: any) => (args.ids as number[]).reduce((acc: number, x: number) => acc + x, 0), + echoRole: async ({ args }: any) => args.r, + echoOverview: async ({ args }: any) => args.o, + }, + }); + + const handler = new RPCApiHandler({ schema: procClient.$schema }); + const handleProcRequest = async (args: any) => { + const r = await handler.handleRequest({ + ...args, + client: procClient, + url: new URL(`http://localhost/${args.path}`), + }); + return { + status: r.status, + body: r.body as any, + data: (r.body as any).data, + error: (r.body as any).error, + meta: (r.body as any).meta, + }; + }; + + // > 2 params object mapping + let r = await handleProcRequest({ + method: 'get', + path: '/$procs/sum3', + query: { q: JSON.stringify({ args: { a: 1, b: 2, c: 3 } }) }, + }); + expect(r.status).toBe(200); + expect(r.data).toBe(6); + + // all optional params can omit payload + r = await handleProcRequest({ method: 'get', path: '/$procs/opt2' }); + expect(r.status).toBe(200); + expect(r.data).toBe(0); + + // array-typed single param via q JSON array + r = await handleProcRequest({ + method: 'get', + path: '/$procs/sumIds', + query: { q: JSON.stringify({ args: { ids: [1, 2, 3] } }) }, + }); + expect(r.status).toBe(200); + expect(r.data).toBe(6); + + // enum param validation + r = await handleProcRequest({ + method: 'get', + path: '/$procs/echoRole', + query: { q: JSON.stringify({ args: { r: 'ADMIN' } }) }, + }); + expect(r.status).toBe(200); + expect(r.data).toBe('ADMIN'); + + // typedef param (object payload) + r = await handleProcRequest({ + method: 'get', + path: '/$procs/echoOverview', + query: { q: JSON.stringify({ args: { o: { total: 123 } } }) }, + }); + expect(r.status).toBe(200); + expect(r.data).toMatchObject({ total: 123 }); + + // wrong type input + r = await handleProcRequest({ + method: 'get', + path: '/$procs/echoInt', + query: { q: JSON.stringify({ args: { x: 'x' } }) }, + }); + expect(r.status).toBe(422); + expect(r.error?.message).toMatch(/invalid input/i); + + // invalid args payload type + r = await handleProcRequest({ + method: 'get', + path: '/$procs/sum3', + query: { q: JSON.stringify({ args: [1, 2, 3, 4] }) }, + }); + expect(r.status).toBe(400); + expect(r.error?.message).toMatch(/args/i); + + // unknown keys + r = await handleProcRequest({ + method: 'get', + path: '/$procs/sum3', + query: { q: JSON.stringify({ args: { a: 1, b: 2, c: 3, d: 4 } }) }, + }); + expect(r.status).toBe(400); + expect(r.error?.message).toMatch(/unknown procedure argument/i); + }); + it('pagination and ordering', async () => { const handleRequest = makeHandler(); diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 80ec8ffb0..80f8221cc 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "3.1.1", + "version": "3.2.0", "description": "ZenStack Test Tools", "type": "module", "scripts": { diff --git a/packages/testtools/src/client.ts b/packages/testtools/src/client.ts index cd6be7472..696596618 100644 --- a/packages/testtools/src/client.ts +++ b/packages/testtools/src/client.ts @@ -205,7 +205,7 @@ export async function createTestClient( fs.writeFileSync(path.resolve(workDir!, 'schema.prisma'), prismaSchemaText); execSync('npx prisma db push --schema ./schema.prisma --skip-generate --force-reset', { cwd: workDir, - stdio: 'ignore', + stdio: options.debug ? 'inherit' : 'ignore', }); } else { if (provider === 'postgresql') { diff --git a/packages/zod/package.json b/packages/zod/package.json index 2b832c9d9..736a3982c 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/zod", - "version": "3.1.1", + "version": "3.2.0", "description": "", "type": "module", "main": "index.js", @@ -10,6 +10,9 @@ "lint": "eslint src --ext ts" }, "keywords": [], + "files": [ + "dist" + ], "author": "", "license": "MIT", "exports": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba5f04d5d..8b686eb02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,9 +69,6 @@ catalogs: react-dom: specifier: 19.2.0 version: 19.2.0 - sql.js: - specifier: ^1.13.0 - version: 1.13.0 svelte: specifier: 5.45.6 version: 5.45.6 @@ -183,6 +180,9 @@ importers: '@zenstackhq/sdk': specifier: workspace:* version: link:../sdk + chokidar: + specifier: ^5.0.0 + version: 5.0.0 colors: specifier: 1.4.0 version: 1.4.0 @@ -495,6 +495,9 @@ importers: pg: specifier: 'catalog:' version: 8.16.3 + postgres-array: + specifier: ^3.0.4 + version: 3.0.4 sql.js: specifier: 'catalog:' version: 1.13.0 @@ -6886,8 +6889,8 @@ packages: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - postgres-array@3.0.2: - resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + postgres-array@3.0.4: + resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==} engines: {node: '>=12'} postgres-bytea@1.0.0: @@ -12435,7 +12438,7 @@ snapshots: eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.29.0(jiti@2.6.1)) @@ -12468,7 +12471,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -12483,7 +12486,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -14507,7 +14510,7 @@ snapshots: dependencies: pg-int8: 1.0.1 pg-numeric: 1.0.2 - postgres-array: 3.0.2 + postgres-array: 3.0.4 postgres-bytea: 3.0.0 postgres-date: 2.1.0 postgres-interval: 3.0.0 @@ -14756,7 +14759,7 @@ snapshots: postgres-array@2.0.0: {} - postgres-array@3.0.2: {} + postgres-array@3.0.4: {} postgres-bytea@1.0.0: {} diff --git a/samples/next.js/app/api/model/[...path]/route.ts b/samples/next.js/app/api/model/[...path]/route.ts index 49ba5ff5f..0c1cfd813 100644 --- a/samples/next.js/app/api/model/[...path]/route.ts +++ b/samples/next.js/app/api/model/[...path]/route.ts @@ -4,7 +4,7 @@ import { RPCApiHandler } from '@zenstackhq/server/api'; import { NextRequestHandler } from '@zenstackhq/server/next'; const handler = NextRequestHandler({ - apiHandler: new RPCApiHandler({ schema }), + apiHandler: new RPCApiHandler({ schema, log: ['debug', 'error'] }), // fully open ZenStackClient is used here for demo purposes only, in a real application, // you should use one with access policies enabled getClient: () => db, diff --git a/samples/next.js/app/feeds/page.tsx b/samples/next.js/app/feeds/page.tsx new file mode 100644 index 000000000..b12f4b1d9 --- /dev/null +++ b/samples/next.js/app/feeds/page.tsx @@ -0,0 +1,59 @@ +'use client'; + +import { schema } from '@/zenstack/schema-lite'; +import { useClientQueries } from '@zenstackhq/tanstack-query/react'; +import Link from 'next/link'; + +export default function FeedsPage() { + const clientQueries = useClientQueries(schema); + const { data: posts, isLoading, error } = clientQueries.$procs.listPublicPosts.useQuery(); + + return ( +
+

+ Public Feeds +

+ + + ← Back to Home + + + {isLoading &&
Loading public posts...
} + + {error && ( +
+ Error loading posts: {error instanceof Error ? error.message : 'Unknown error'} +
+ )} + + {!isLoading && !error && posts && posts.length === 0 && ( +
No public posts available yet.
+ )} + + {posts && posts.length > 0 && ( +
    + {posts.map((post) => ( +
  • +

    {post.title}

    +

    + Published on {new Date(post.createdAt).toLocaleDateString()} +

    +
  • + ))} +
+ )} + + {posts && posts.length > 0 && ( +
+ Showing {posts.length} public {posts.length === 1 ? 'post' : 'posts'} +
+ )} +
+ ); +} diff --git a/samples/next.js/app/layout.tsx b/samples/next.js/app/layout.tsx index 7a6da3a52..89d573970 100644 --- a/samples/next.js/app/layout.tsx +++ b/samples/next.js/app/layout.tsx @@ -1,4 +1,5 @@ import { Geist, Geist_Mono } from 'next/font/google'; +import Image from 'next/image'; import './globals.css'; import Providers from './providers'; @@ -20,7 +21,21 @@ export default function RootLayout({ return ( - {children} + +
+
+ Next.js logo + {children} +
+
+
); diff --git a/samples/next.js/app/page.tsx b/samples/next.js/app/page.tsx index 4372f2a2c..9dee7770c 100644 --- a/samples/next.js/app/page.tsx +++ b/samples/next.js/app/page.tsx @@ -4,7 +4,7 @@ import { Post } from '@/zenstack/models'; import { schema } from '@/zenstack/schema-lite'; import { FetchFn, useClientQueries } from '@zenstackhq/tanstack-query/react'; import { LoremIpsum } from 'lorem-ipsum'; -import Image from 'next/image'; +import Link from 'next/link'; import { useState } from 'react'; const lorem = new LoremIpsum({ wordsPerSentence: { max: 6, min: 4 } }); @@ -74,97 +74,107 @@ export default function Home() { } return ( -
-
- Next.js logo -
-

- My Awesome Blog -

- - - -
-
Current users
-
- {users?.map((user) => ( -
- {user.email} -
- ))} +
+

+ My Awesome Blog +

+ +
+ + View Public Feeds + + + Sign Up + +
+ + + +
+
Current users
+
+ {users?.map((user) => ( +
+ {user.email}
-
- -
- - - - - -
- -
    - {posts?.map((post) => ( -
  • -
    -
    -

    {post.title}

    - {post.$optimistic ? pending : null} -
    -
    - - -
    -
    - {post.$optimistic ? null : ( -

    - by {post.author.name} {!post.published ? '(Draft)' : ''} -

    - )} -
  • - ))} -
+ ))}
-
+
+ +
+ + + + + +
+ +
    + {posts?.map((post) => ( +
  • +
    +
    +

    {post.title}

    + {post.$optimistic ? pending : null} +
    +
    + + +
    +
    + {post.$optimistic ? null : ( +

    + by {post.author.name} {!post.published ? '(Draft)' : ''} +

    + )} +
  • + ))} +
); } diff --git a/samples/next.js/app/signup/page.tsx b/samples/next.js/app/signup/page.tsx new file mode 100644 index 000000000..ace8e6215 --- /dev/null +++ b/samples/next.js/app/signup/page.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { schema } from '@/zenstack/schema-lite'; +import { useClientQueries } from '@zenstackhq/tanstack-query/react'; +import Link from 'next/link'; +import { FormEvent, useState } from 'react'; + +export default function SignupPage() { + const [email, setEmail] = useState(''); + const [successMessage, setSuccessMessage] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + + const clientQueries = useClientQueries(schema); + const signUpMutation = clientQueries.$procs.signUp.useMutation(); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + setSuccessMessage(''); + setErrorMessage(''); + + signUpMutation.mutate( + { args: { email } }, + { + onSuccess: (user) => { + setSuccessMessage(`Successfully created user: ${user.email}`); + setEmail(''); + }, + onError: (error) => { + setErrorMessage(error instanceof Error ? error.message : 'Failed to sign up'); + }, + }, + ); + }; + + return ( +
+

Sign Up

+ + + ← Back to Home + + +
+
+ + setEmail(e.target.value)} + required + disabled={signUpMutation.isPending} + placeholder="user@example.com" + className="rounded-md border border-gray-300 px-4 py-2 text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed dark:bg-zinc-900 dark:border-zinc-700 dark:text-zinc-50 dark:placeholder-zinc-500" + /> +
+ + +
+ + {successMessage && ( +
+ {successMessage} +
+ )} + + {errorMessage && ( +
+ {errorMessage} +
+ )} +
+ ); +} diff --git a/samples/next.js/lib/db.ts b/samples/next.js/lib/db.ts index 688e78863..1ba1422a0 100644 --- a/samples/next.js/lib/db.ts +++ b/samples/next.js/lib/db.ts @@ -1,10 +1,25 @@ import { schema } from '@/zenstack/schema'; import { ZenStackClient } from '@zenstackhq/orm'; +import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite'; import SQLite from 'better-sqlite3'; -import { SqliteDialect } from 'kysely'; export const db = new ZenStackClient(schema, { dialect: new SqliteDialect({ database: new SQLite('./zenstack/dev.db'), }), + procedures: { + signUp: ({ client, args }) => + client.user.create({ + data: { ...args }, + }), + listPublicPosts: ({ client }) => + client.post.findMany({ + where: { + published: true, + }, + orderBy: { + updatedAt: 'desc', + }, + }), + }, }); diff --git a/samples/next.js/zenstack/input.ts b/samples/next.js/zenstack/input.ts index b2cd96ee4..72c04fe53 100644 --- a/samples/next.js/zenstack/input.ts +++ b/samples/next.js/zenstack/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema-lite"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; diff --git a/samples/next.js/zenstack/schema-lite.ts b/samples/next.js/zenstack/schema-lite.ts index 6153abe9d..7308e184d 100644 --- a/samples/next.js/zenstack/schema-lite.ts +++ b/samples/next.js/zenstack/schema-lite.ts @@ -101,6 +101,20 @@ export class SchemaType implements SchemaDef { } } as const; authType = "User" as const; + procedures = { + signUp: { + params: { + email: { name: "email", type: "String" } + }, + returnType: "User", + mutation: true + }, + listPublicPosts: { + params: {}, + returnType: "Post", + returnArray: true + } + } as const; plugins = {}; } export const schema = new SchemaType(); diff --git a/samples/next.js/zenstack/schema.ts b/samples/next.js/zenstack/schema.ts index c7d690ed5..695ee053a 100644 --- a/samples/next.js/zenstack/schema.ts +++ b/samples/next.js/zenstack/schema.ts @@ -110,6 +110,20 @@ export class SchemaType implements SchemaDef { } } as const; authType = "User" as const; + procedures = { + signUp: { + params: { + email: { name: "email", type: "String" } + }, + returnType: "User", + mutation: true + }, + listPublicPosts: { + params: {}, + returnType: "Post", + returnArray: true + } + } as const; plugins = {}; } export const schema = new SchemaType(); diff --git a/samples/next.js/zenstack/schema.zmodel b/samples/next.js/zenstack/schema.zmodel deleted file mode 100644 index e1775e122..000000000 --- a/samples/next.js/zenstack/schema.zmodel +++ /dev/null @@ -1,25 +0,0 @@ -datasource db { - provider = 'sqlite' - url = 'file:./dev.db' -} - -/// User model -model User { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - email String @unique - name String? - posts Post[] -} - -/// Post model -model Post { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - title String - published Boolean @default(false) - author User @relation(fields: [authorId], references: [id], onUpdate: Cascade, onDelete: Cascade) - authorId String -} diff --git a/samples/next.js/zenstack/schema.zmodel b/samples/next.js/zenstack/schema.zmodel new file mode 120000 index 000000000..5baeec7af --- /dev/null +++ b/samples/next.js/zenstack/schema.zmodel @@ -0,0 +1 @@ +../../shared/schema.zmodel \ No newline at end of file diff --git a/samples/next.js/zenstack/seed.ts b/samples/next.js/zenstack/seed.ts index 6e02d80d1..14004e7d8 100644 --- a/samples/next.js/zenstack/seed.ts +++ b/samples/next.js/zenstack/seed.ts @@ -1,17 +1,7 @@ -import { ZenStackClient } from '@zenstackhq/orm'; -import SQLite from 'better-sqlite3'; -import { SqliteDialect } from 'kysely'; -import { schema } from './schema'; +import { db } from '@/lib/db'; async function main() { - const db = new ZenStackClient(schema, { - dialect: new SqliteDialect({ - database: new SQLite('./zenstack/dev.db'), - }), - }); - await db.user.deleteMany(); - await db.user.createMany({ data: [ { id: '1', name: 'Alice', email: 'alice@example.com' }, diff --git a/samples/nuxt/app/app.vue b/samples/nuxt/app/app.vue index 4512f245b..5e39f2ae9 100644 --- a/samples/nuxt/app/app.vue +++ b/samples/nuxt/app/app.vue @@ -1,147 +1,5 @@ - - diff --git a/samples/nuxt/app/layouts/default.vue b/samples/nuxt/app/layouts/default.vue new file mode 100644 index 000000000..6a1f2ce6a --- /dev/null +++ b/samples/nuxt/app/layouts/default.vue @@ -0,0 +1,10 @@ + diff --git a/samples/nuxt/app/pages/feeds.vue b/samples/nuxt/app/pages/feeds.vue new file mode 100644 index 000000000..24169b009 --- /dev/null +++ b/samples/nuxt/app/pages/feeds.vue @@ -0,0 +1,52 @@ + + + diff --git a/samples/nuxt/app/pages/index.vue b/samples/nuxt/app/pages/index.vue new file mode 100644 index 000000000..068d2f962 --- /dev/null +++ b/samples/nuxt/app/pages/index.vue @@ -0,0 +1,155 @@ + + + diff --git a/samples/nuxt/app/pages/signup.vue b/samples/nuxt/app/pages/signup.vue new file mode 100644 index 000000000..72304f33e --- /dev/null +++ b/samples/nuxt/app/pages/signup.vue @@ -0,0 +1,80 @@ + + + diff --git a/samples/nuxt/app/plugins/tanstack-query.ts b/samples/nuxt/app/plugins/tanstack-query.ts index 2c380de01..3df30897f 100644 --- a/samples/nuxt/app/plugins/tanstack-query.ts +++ b/samples/nuxt/app/plugins/tanstack-query.ts @@ -11,8 +11,8 @@ export default defineNuxtPlugin((nuxtApp) => { setup() { provideQuerySettingsContext({ endpoint: '/api/model', - logging: true + logging: true, }); - } + }, }); }); diff --git a/samples/nuxt/server/api/model/[...].ts b/samples/nuxt/server/api/model/[...].ts index 7483280a6..7e797d031 100644 --- a/samples/nuxt/server/api/model/[...].ts +++ b/samples/nuxt/server/api/model/[...].ts @@ -4,7 +4,7 @@ import { db } from '~~/server/utils/db'; import { schema } from '~~/zenstack/schema'; const handler = createEventHandler({ - apiHandler: new RPCApiHandler({ schema }), + apiHandler: new RPCApiHandler({ schema, log: ['debug', 'error'] }), // fully open ZenStackClient is used here for demo purposes only, in a real application, // you should use one with access policies enabled getClient: () => db, diff --git a/samples/nuxt/server/utils/db.ts b/samples/nuxt/server/utils/db.ts index 86aa52489..5513bc004 100644 --- a/samples/nuxt/server/utils/db.ts +++ b/samples/nuxt/server/utils/db.ts @@ -1,10 +1,25 @@ import { ZenStackClient } from '@zenstackhq/orm'; +import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite'; import SQLite from 'better-sqlite3'; -import { SqliteDialect } from 'kysely'; -import { schema } from '~~/zenstack/schema'; +import { schema } from '../../zenstack/schema'; export const db = new ZenStackClient(schema, { dialect: new SqliteDialect({ database: new SQLite('./zenstack/dev.db'), }), + procedures: { + signUp: ({ client, args }) => + client.user.create({ + data: { ...args }, + }), + listPublicPosts: ({ client }) => + client.post.findMany({ + where: { + published: true, + }, + orderBy: { + updatedAt: 'desc', + }, + }), + }, }); diff --git a/samples/nuxt/zenstack/input.ts b/samples/nuxt/zenstack/input.ts index b2cd96ee4..72c04fe53 100644 --- a/samples/nuxt/zenstack/input.ts +++ b/samples/nuxt/zenstack/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema-lite"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; diff --git a/samples/nuxt/zenstack/schema-lite.ts b/samples/nuxt/zenstack/schema-lite.ts index 6153abe9d..7308e184d 100644 --- a/samples/nuxt/zenstack/schema-lite.ts +++ b/samples/nuxt/zenstack/schema-lite.ts @@ -101,6 +101,20 @@ export class SchemaType implements SchemaDef { } } as const; authType = "User" as const; + procedures = { + signUp: { + params: { + email: { name: "email", type: "String" } + }, + returnType: "User", + mutation: true + }, + listPublicPosts: { + params: {}, + returnType: "Post", + returnArray: true + } + } as const; plugins = {}; } export const schema = new SchemaType(); diff --git a/samples/nuxt/zenstack/schema.ts b/samples/nuxt/zenstack/schema.ts index c7d690ed5..695ee053a 100644 --- a/samples/nuxt/zenstack/schema.ts +++ b/samples/nuxt/zenstack/schema.ts @@ -110,6 +110,20 @@ export class SchemaType implements SchemaDef { } } as const; authType = "User" as const; + procedures = { + signUp: { + params: { + email: { name: "email", type: "String" } + }, + returnType: "User", + mutation: true + }, + listPublicPosts: { + params: {}, + returnType: "Post", + returnArray: true + } + } as const; plugins = {}; } export const schema = new SchemaType(); diff --git a/samples/nuxt/zenstack/schema.zmodel b/samples/nuxt/zenstack/schema.zmodel deleted file mode 100644 index e1775e122..000000000 --- a/samples/nuxt/zenstack/schema.zmodel +++ /dev/null @@ -1,25 +0,0 @@ -datasource db { - provider = 'sqlite' - url = 'file:./dev.db' -} - -/// User model -model User { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - email String @unique - name String? - posts Post[] -} - -/// Post model -model Post { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - title String - published Boolean @default(false) - author User @relation(fields: [authorId], references: [id], onUpdate: Cascade, onDelete: Cascade) - authorId String -} diff --git a/samples/nuxt/zenstack/schema.zmodel b/samples/nuxt/zenstack/schema.zmodel new file mode 120000 index 000000000..5baeec7af --- /dev/null +++ b/samples/nuxt/zenstack/schema.zmodel @@ -0,0 +1 @@ +../../shared/schema.zmodel \ No newline at end of file diff --git a/samples/nuxt/zenstack/seed.ts b/samples/nuxt/zenstack/seed.ts index 6e02d80d1..7a4acb63b 100644 --- a/samples/nuxt/zenstack/seed.ts +++ b/samples/nuxt/zenstack/seed.ts @@ -1,17 +1,7 @@ -import { ZenStackClient } from '@zenstackhq/orm'; -import SQLite from 'better-sqlite3'; -import { SqliteDialect } from 'kysely'; -import { schema } from './schema'; +import { db } from '../server/utils/db'; async function main() { - const db = new ZenStackClient(schema, { - dialect: new SqliteDialect({ - database: new SQLite('./zenstack/dev.db'), - }), - }); - await db.user.deleteMany(); - await db.user.createMany({ data: [ { id: '1', name: 'Alice', email: 'alice@example.com' }, diff --git a/samples/orm/main.ts b/samples/orm/main.ts index 59e814c13..4b7f82ef1 100644 --- a/samples/orm/main.ts +++ b/samples/orm/main.ts @@ -16,6 +16,18 @@ async function main() { .select(({ fn }) => fn.countAll().as('postCount')), }, }, + procedures: { + signUp: ({ client, args }) => + client.user.create({ + data: { ...args }, + }), + listPublicPosts: ({ client }) => + client.post.findMany({ + where: { + published: true, + }, + }), + }, }).$use({ id: 'cost-logger', onQuery: async ({ model, operation, args, proceed }) => { @@ -101,6 +113,12 @@ async function main() { console.log('Posts readable to', user2.email); console.table(await user2Db.post.findMany({ select: { title: true, published: true } })); + + const newUser = await authDb.$procs.signUp({ args: { email: 'new@zenstack.dev', name: 'New User' } }); + console.log('New user signed up via procedure:', newUser); + + const publicPosts = await authDb.$procs.listPublicPosts(); + console.log('Public posts via procedure:', publicPosts); } main(); diff --git a/samples/orm/package.json b/samples/orm/package.json index ba8827cd2..7017177ed 100644 --- a/samples/orm/package.json +++ b/samples/orm/package.json @@ -1,6 +1,6 @@ { "name": "sample-blog", - "version": "3.1.1", + "version": "3.2.0", "description": "", "main": "index.js", "private": true, diff --git a/samples/orm/zenstack/input.ts b/samples/orm/zenstack/input.ts index fcd198d49..cfc174c06 100644 --- a/samples/orm/zenstack/input.ts +++ b/samples/orm/zenstack/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; @@ -51,6 +53,7 @@ export type ProfileGetPayload; 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">; diff --git a/samples/orm/zenstack/schema.ts b/samples/orm/zenstack/schema.ts index deb07cb9c..e3c02e5c6 100644 --- a/samples/orm/zenstack/schema.ts +++ b/samples/orm/zenstack/schema.ts @@ -239,6 +239,22 @@ export class SchemaType implements SchemaDef { } } as const; authType = "User" as const; + procedures = { + signUp: { + params: { + email: { name: "email", type: "String" }, + name: { name: "name", optional: true, type: "String" }, + role: { name: "role", optional: true, type: "Role" } + }, + returnType: "User", + mutation: true + }, + listPublicPosts: { + params: {}, + returnType: "Post", + returnArray: true + } + } as const; plugins = {}; } export const schema = new SchemaType(); diff --git a/samples/orm/zenstack/schema.zmodel b/samples/orm/zenstack/schema.zmodel index 3669d7995..2567801af 100644 --- a/samples/orm/zenstack/schema.zmodel +++ b/samples/orm/zenstack/schema.zmodel @@ -47,10 +47,13 @@ model Profile with CommonFields { model Post with CommonFields { title String content String - published Boolean @default(false) - author User @relation(fields: [authorId], references: [id]) + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) authorId String @@allow('read', published) @@allow('all', author == auth()) } + +mutation procedure signUp(email: String, name: String?, role: Role?): User +procedure listPublicPosts(): Post[] diff --git a/samples/shared/schema.zmodel b/samples/shared/schema.zmodel new file mode 100644 index 000000000..a0d25d8a8 --- /dev/null +++ b/samples/shared/schema.zmodel @@ -0,0 +1,28 @@ +datasource db { + provider = 'sqlite' + url = 'file:./dev.db' +} + +/// User model +model User { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + email String @unique + name String? + posts Post[] +} + +/// Post model +model Post { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + title String + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id], onUpdate: Cascade, onDelete: Cascade) + authorId String +} + +mutation procedure signUp(email: String): User +procedure listPublicPosts(): Post[] diff --git a/samples/sveltekit/src/app.d.ts b/samples/sveltekit/src/app.d.ts index da08e6da5..520c4217a 100644 --- a/samples/sveltekit/src/app.d.ts +++ b/samples/sveltekit/src/app.d.ts @@ -1,13 +1,13 @@ // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } export {}; diff --git a/samples/sveltekit/src/lib/db.ts b/samples/sveltekit/src/lib/db.ts index bbf70b8c0..ab4727df5 100644 --- a/samples/sveltekit/src/lib/db.ts +++ b/samples/sveltekit/src/lib/db.ts @@ -7,4 +7,19 @@ export const db = new ZenStackClient(schema, { dialect: new SqliteDialect({ database: new SQLite("./src/zenstack/dev.db"), }), + procedures: { + signUp: ({ client, args }) => + client.user.create({ + data: { ...args }, + }), + listPublicPosts: ({ client }) => + client.post.findMany({ + where: { + published: true, + }, + orderBy: { + updatedAt: "desc", + }, + }), + }, }); diff --git a/samples/sveltekit/src/routes/+layout.svelte b/samples/sveltekit/src/routes/+layout.svelte index 77a8e8008..e1770a856 100644 --- a/samples/sveltekit/src/routes/+layout.svelte +++ b/samples/sveltekit/src/routes/+layout.svelte @@ -1,23 +1,32 @@ - {@render children()} +
+
+ SvelteKit logo + {@render children()} +
+
diff --git a/samples/sveltekit/src/routes/+page.svelte b/samples/sveltekit/src/routes/+page.svelte index 5725bf796..4f479b426 100644 --- a/samples/sveltekit/src/routes/+page.svelte +++ b/samples/sveltekit/src/routes/+page.svelte @@ -19,15 +19,14 @@ const clientQueries = useClientQueries(schema, () => ({ fetch: customFetch })); const users = clientQueries.user.useFindMany(); - const posts = - clientQueries.post.useFindMany(() => ( - { - where: showPublishedOnly ? { published: true } : undefined, - orderBy: { createdAt: 'desc' }, - include: { author: true } - }), - () => ({ enabled: enableFetch }) - ) + const posts = clientQueries.post.useFindMany( + () => ({ + where: showPublishedOnly ? { published: true } : undefined, + orderBy: { createdAt: 'desc' }, + include: { author: true } + }), + () => ({ enabled: enableFetch }) + ); const createPost = clientQueries.post.useCreate(() => ({ optimisticUpdate: optimistic })); const deletePost = clientQueries.post.useDelete(() => ({ optimisticUpdate: optimistic })); @@ -70,100 +69,104 @@ {#if users.isFetched && (!users.data || users.data.length === 0)}
No users found. Please run "pnpm db:init" to seed the database.
{:else} -
-
+

- SvelteKit logo -
-

- My Awesome Blog -

- - - -
-
Current users
-
- {#if users.isLoading} -
Loading users...
- {:else if users.isError} -
Error loading users: {users.error.message}
- {:else} - {#each users.data as user} -
- {user.email} -
- {/each} - {/if} -
-
- -
- - - - - -
- -
    - {#if posts.data} - {#each posts.data as post} -
  • -
    -
    -

    {post.title}

    - {#if post.$optimistic} - pending - {/if} -
    -
    - - -
    -
    - {#if !post.$optimistic} -

    - by {post.author.name} - {!post.published ? '(Draft)' : ''} -

    - {/if} -
  • - {/each} - {/if} -
+ My Awesome Blog +

+ + + + + +
+
Current users
+
+ {#if users.isLoading} +
Loading users...
+ {:else if users.isError} +
Error loading users: {users.error.message}
+ {:else} + {#each users.data as user} +
+ {user.email} +
+ {/each} + {/if}
-
+
+ +
+ + + + + +
+ +
    + {#if posts.data} + {#each posts.data as post} +
  • +
    +
    +

    {post.title}

    + {#if post.$optimistic} + pending + {/if} +
    +
    + + +
    +
    + {#if !post.$optimistic} +

    + by {post.author.name} + {!post.published ? '(Draft)' : ''} +

    + {/if} +
  • + {/each} + {/if} +
{/if} diff --git a/samples/sveltekit/src/routes/feeds/+page.svelte b/samples/sveltekit/src/routes/feeds/+page.svelte new file mode 100644 index 000000000..c84617b41 --- /dev/null +++ b/samples/sveltekit/src/routes/feeds/+page.svelte @@ -0,0 +1,57 @@ + + +
+

+ Public Feeds +

+ + + ← Back to Home + + + {#if isLoading} +
Loading public posts...
+ {/if} + + {#if error} +
+ Error loading posts: {error instanceof Error ? error.message : 'Unknown error'} +
+ {/if} + + {#if !isLoading && !error && posts && posts.length === 0} +
No public posts available yet.
+ {/if} + + {#if posts && posts.length > 0} +
    + {#each posts as post} +
  • +

    {post.title}

    +

    + Published on {new Date(post.createdAt).toLocaleDateString()} +

    +
  • + {/each} +
+ {/if} + + {#if posts && posts.length > 0} +
+ Showing {posts.length} public {posts.length === 1 ? 'post' : 'posts'} +
+ {/if} +
diff --git a/samples/sveltekit/src/routes/signup/+page.svelte b/samples/sveltekit/src/routes/signup/+page.svelte new file mode 100644 index 000000000..25efb0e78 --- /dev/null +++ b/samples/sveltekit/src/routes/signup/+page.svelte @@ -0,0 +1,86 @@ + + +
+

+ Sign Up +

+ + + ← Back to Home + + +
+
+ + +
+ + +
+ + {#if successMessage} +
+ {successMessage} +
+ {/if} + + {#if errorMessage} +
+ {errorMessage} +
+ {/if} +
diff --git a/samples/sveltekit/src/zenstack/input.ts b/samples/sveltekit/src/zenstack/input.ts index b2cd96ee4..72c04fe53 100644 --- a/samples/sveltekit/src/zenstack/input.ts +++ b/samples/sveltekit/src/zenstack/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema-lite"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; diff --git a/samples/sveltekit/src/zenstack/schema-lite.ts b/samples/sveltekit/src/zenstack/schema-lite.ts index 6153abe9d..7308e184d 100644 --- a/samples/sveltekit/src/zenstack/schema-lite.ts +++ b/samples/sveltekit/src/zenstack/schema-lite.ts @@ -101,6 +101,20 @@ export class SchemaType implements SchemaDef { } } as const; authType = "User" as const; + procedures = { + signUp: { + params: { + email: { name: "email", type: "String" } + }, + returnType: "User", + mutation: true + }, + listPublicPosts: { + params: {}, + returnType: "Post", + returnArray: true + } + } as const; plugins = {}; } export const schema = new SchemaType(); diff --git a/samples/sveltekit/src/zenstack/schema.ts b/samples/sveltekit/src/zenstack/schema.ts index c7d690ed5..695ee053a 100644 --- a/samples/sveltekit/src/zenstack/schema.ts +++ b/samples/sveltekit/src/zenstack/schema.ts @@ -110,6 +110,20 @@ export class SchemaType implements SchemaDef { } } as const; authType = "User" as const; + procedures = { + signUp: { + params: { + email: { name: "email", type: "String" } + }, + returnType: "User", + mutation: true + }, + listPublicPosts: { + params: {}, + returnType: "Post", + returnArray: true + } + } as const; plugins = {}; } export const schema = new SchemaType(); diff --git a/samples/sveltekit/src/zenstack/schema.zmodel b/samples/sveltekit/src/zenstack/schema.zmodel deleted file mode 100644 index e1775e122..000000000 --- a/samples/sveltekit/src/zenstack/schema.zmodel +++ /dev/null @@ -1,25 +0,0 @@ -datasource db { - provider = 'sqlite' - url = 'file:./dev.db' -} - -/// User model -model User { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - email String @unique - name String? - posts Post[] -} - -/// Post model -model Post { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - title String - published Boolean @default(false) - author User @relation(fields: [authorId], references: [id], onUpdate: Cascade, onDelete: Cascade) - authorId String -} diff --git a/samples/sveltekit/src/zenstack/schema.zmodel b/samples/sveltekit/src/zenstack/schema.zmodel new file mode 120000 index 000000000..67e69c0f9 --- /dev/null +++ b/samples/sveltekit/src/zenstack/schema.zmodel @@ -0,0 +1 @@ +../../../shared/schema.zmodel \ No newline at end of file diff --git a/scripts/bump-version.ts b/scripts/bump-version.ts index 204c4a38c..c89a4775e 100644 --- a/scripts/bump-version.ts +++ b/scripts/bump-version.ts @@ -4,7 +4,7 @@ import * as path from 'node:path'; import * as yaml from 'yaml'; import { fileURLToPath } from 'node:url'; -const excludes = ['packages/ide/vscode/package.json']; +const excludes: string[] = []; const _dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); @@ -36,7 +36,7 @@ function incrementVersion(version: string, type: 'patch' | 'minor' = 'patch'): s const parts = version.split('.'); if (parts.length !== 3) throw new Error(`Invalid version format: ${version}`); - const [major, minor, patch] = parts.map(p => parseInt(p, 10)); + const [major, minor, patch] = parts.map((p) => parseInt(p, 10)); if (isNaN(major) || isNaN(minor) || isNaN(patch)) { throw new Error(`Invalid version: ${version}`); } diff --git a/tests/e2e/apps/rally/zenstack/input.ts b/tests/e2e/apps/rally/zenstack/input.ts index 27012a3f3..cd816ecfa 100644 --- a/tests/e2e/apps/rally/zenstack/input.ts +++ b/tests/e2e/apps/rally/zenstack/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 AccountFindManyArgs = $FindManyArgs<$Schema, "Account">; export type AccountFindUniqueArgs = $FindUniqueArgs<$Schema, "Account">; export type AccountFindFirstArgs = $FindFirstArgs<$Schema, "Account">; +export type AccountExistsArgs = $ExistsArgs<$Schema, "Account">; export type AccountCreateArgs = $CreateArgs<$Schema, "Account">; export type AccountCreateManyArgs = $CreateManyArgs<$Schema, "Account">; export type AccountCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Account">; @@ -31,6 +32,7 @@ export type AccountGetPayload; 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">; @@ -51,6 +53,7 @@ export type UserGetPayload; export type VerificationTokenFindUniqueArgs = $FindUniqueArgs<$Schema, "VerificationToken">; export type VerificationTokenFindFirstArgs = $FindFirstArgs<$Schema, "VerificationToken">; +export type VerificationTokenExistsArgs = $ExistsArgs<$Schema, "VerificationToken">; export type VerificationTokenCreateArgs = $CreateArgs<$Schema, "VerificationToken">; export type VerificationTokenCreateManyArgs = $CreateManyArgs<$Schema, "VerificationToken">; export type VerificationTokenCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "VerificationToken">; @@ -71,6 +74,7 @@ export type VerificationTokenGetPayload; export type SessionFindUniqueArgs = $FindUniqueArgs<$Schema, "Session">; export type SessionFindFirstArgs = $FindFirstArgs<$Schema, "Session">; +export type SessionExistsArgs = $ExistsArgs<$Schema, "Session">; export type SessionCreateArgs = $CreateArgs<$Schema, "Session">; export type SessionCreateManyArgs = $CreateManyArgs<$Schema, "Session">; export type SessionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Session">; @@ -91,6 +95,7 @@ export type SessionGetPayload; export type VerificationFindUniqueArgs = $FindUniqueArgs<$Schema, "Verification">; export type VerificationFindFirstArgs = $FindFirstArgs<$Schema, "Verification">; +export type VerificationExistsArgs = $ExistsArgs<$Schema, "Verification">; export type VerificationCreateArgs = $CreateArgs<$Schema, "Verification">; export type VerificationCreateManyArgs = $CreateManyArgs<$Schema, "Verification">; export type VerificationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Verification">; @@ -111,6 +116,7 @@ export type VerificationGetPayload; export type PollFindUniqueArgs = $FindUniqueArgs<$Schema, "Poll">; export type PollFindFirstArgs = $FindFirstArgs<$Schema, "Poll">; +export type PollExistsArgs = $ExistsArgs<$Schema, "Poll">; export type PollCreateArgs = $CreateArgs<$Schema, "Poll">; export type PollCreateManyArgs = $CreateManyArgs<$Schema, "Poll">; export type PollCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Poll">; @@ -131,6 +137,7 @@ export type PollGetPayload; export type WatcherFindUniqueArgs = $FindUniqueArgs<$Schema, "Watcher">; export type WatcherFindFirstArgs = $FindFirstArgs<$Schema, "Watcher">; +export type WatcherExistsArgs = $ExistsArgs<$Schema, "Watcher">; export type WatcherCreateArgs = $CreateArgs<$Schema, "Watcher">; export type WatcherCreateManyArgs = $CreateManyArgs<$Schema, "Watcher">; export type WatcherCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Watcher">; @@ -151,6 +158,7 @@ export type WatcherGetPayload; export type ParticipantFindUniqueArgs = $FindUniqueArgs<$Schema, "Participant">; export type ParticipantFindFirstArgs = $FindFirstArgs<$Schema, "Participant">; +export type ParticipantExistsArgs = $ExistsArgs<$Schema, "Participant">; export type ParticipantCreateArgs = $CreateArgs<$Schema, "Participant">; export type ParticipantCreateManyArgs = $CreateManyArgs<$Schema, "Participant">; export type ParticipantCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Participant">; @@ -171,6 +179,7 @@ export type ParticipantGetPayload; export type OptionFindUniqueArgs = $FindUniqueArgs<$Schema, "Option">; export type OptionFindFirstArgs = $FindFirstArgs<$Schema, "Option">; +export type OptionExistsArgs = $ExistsArgs<$Schema, "Option">; export type OptionCreateArgs = $CreateArgs<$Schema, "Option">; export type OptionCreateManyArgs = $CreateManyArgs<$Schema, "Option">; export type OptionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Option">; @@ -191,6 +200,7 @@ export type OptionGetPayload; export type VoteFindUniqueArgs = $FindUniqueArgs<$Schema, "Vote">; export type VoteFindFirstArgs = $FindFirstArgs<$Schema, "Vote">; +export type VoteExistsArgs = $ExistsArgs<$Schema, "Vote">; export type VoteCreateArgs = $CreateArgs<$Schema, "Vote">; export type VoteCreateManyArgs = $CreateManyArgs<$Schema, "Vote">; export type VoteCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Vote">; @@ -211,6 +221,7 @@ export type VoteGetPayload; 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">; @@ -231,6 +242,7 @@ export type CommentGetPayload; export type PollViewFindUniqueArgs = $FindUniqueArgs<$Schema, "PollView">; export type PollViewFindFirstArgs = $FindFirstArgs<$Schema, "PollView">; +export type PollViewExistsArgs = $ExistsArgs<$Schema, "PollView">; export type PollViewCreateArgs = $CreateArgs<$Schema, "PollView">; export type PollViewCreateManyArgs = $CreateManyArgs<$Schema, "PollView">; export type PollViewCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "PollView">; @@ -251,6 +263,7 @@ export type PollViewGetPayload; export type SpaceFindUniqueArgs = $FindUniqueArgs<$Schema, "Space">; export type SpaceFindFirstArgs = $FindFirstArgs<$Schema, "Space">; +export type SpaceExistsArgs = $ExistsArgs<$Schema, "Space">; export type SpaceCreateArgs = $CreateArgs<$Schema, "Space">; export type SpaceCreateManyArgs = $CreateManyArgs<$Schema, "Space">; export type SpaceCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Space">; @@ -271,6 +284,7 @@ export type SpaceGetPayload; export type SpaceMemberFindUniqueArgs = $FindUniqueArgs<$Schema, "SpaceMember">; export type SpaceMemberFindFirstArgs = $FindFirstArgs<$Schema, "SpaceMember">; +export type SpaceMemberExistsArgs = $ExistsArgs<$Schema, "SpaceMember">; export type SpaceMemberCreateArgs = $CreateArgs<$Schema, "SpaceMember">; export type SpaceMemberCreateManyArgs = $CreateManyArgs<$Schema, "SpaceMember">; export type SpaceMemberCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SpaceMember">; @@ -291,6 +305,7 @@ export type SpaceMemberGetPayload; export type SpaceMemberInviteFindUniqueArgs = $FindUniqueArgs<$Schema, "SpaceMemberInvite">; export type SpaceMemberInviteFindFirstArgs = $FindFirstArgs<$Schema, "SpaceMemberInvite">; +export type SpaceMemberInviteExistsArgs = $ExistsArgs<$Schema, "SpaceMemberInvite">; export type SpaceMemberInviteCreateArgs = $CreateArgs<$Schema, "SpaceMemberInvite">; export type SpaceMemberInviteCreateManyArgs = $CreateManyArgs<$Schema, "SpaceMemberInvite">; export type SpaceMemberInviteCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SpaceMemberInvite">; @@ -311,6 +326,7 @@ export type SpaceMemberInviteGetPayload; export type SubscriptionFindUniqueArgs = $FindUniqueArgs<$Schema, "Subscription">; export type SubscriptionFindFirstArgs = $FindFirstArgs<$Schema, "Subscription">; +export type SubscriptionExistsArgs = $ExistsArgs<$Schema, "Subscription">; export type SubscriptionCreateArgs = $CreateArgs<$Schema, "Subscription">; export type SubscriptionCreateManyArgs = $CreateManyArgs<$Schema, "Subscription">; export type SubscriptionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Subscription">; @@ -331,6 +347,7 @@ export type SubscriptionGetPayload; export type PaymentMethodFindUniqueArgs = $FindUniqueArgs<$Schema, "PaymentMethod">; export type PaymentMethodFindFirstArgs = $FindFirstArgs<$Schema, "PaymentMethod">; +export type PaymentMethodExistsArgs = $ExistsArgs<$Schema, "PaymentMethod">; export type PaymentMethodCreateArgs = $CreateArgs<$Schema, "PaymentMethod">; export type PaymentMethodCreateManyArgs = $CreateManyArgs<$Schema, "PaymentMethod">; export type PaymentMethodCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "PaymentMethod">; @@ -351,6 +368,7 @@ export type PaymentMethodGetPayload; export type ScheduledEventFindUniqueArgs = $FindUniqueArgs<$Schema, "ScheduledEvent">; export type ScheduledEventFindFirstArgs = $FindFirstArgs<$Schema, "ScheduledEvent">; +export type ScheduledEventExistsArgs = $ExistsArgs<$Schema, "ScheduledEvent">; export type ScheduledEventCreateArgs = $CreateArgs<$Schema, "ScheduledEvent">; export type ScheduledEventCreateManyArgs = $CreateManyArgs<$Schema, "ScheduledEvent">; export type ScheduledEventCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ScheduledEvent">; @@ -371,6 +389,7 @@ export type ScheduledEventGetPayload; export type RescheduledEventDateFindUniqueArgs = $FindUniqueArgs<$Schema, "RescheduledEventDate">; export type RescheduledEventDateFindFirstArgs = $FindFirstArgs<$Schema, "RescheduledEventDate">; +export type RescheduledEventDateExistsArgs = $ExistsArgs<$Schema, "RescheduledEventDate">; export type RescheduledEventDateCreateArgs = $CreateArgs<$Schema, "RescheduledEventDate">; export type RescheduledEventDateCreateManyArgs = $CreateManyArgs<$Schema, "RescheduledEventDate">; export type RescheduledEventDateCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RescheduledEventDate">; @@ -391,6 +410,7 @@ export type RescheduledEventDateGetPayload; export type ScheduledEventInviteFindUniqueArgs = $FindUniqueArgs<$Schema, "ScheduledEventInvite">; export type ScheduledEventInviteFindFirstArgs = $FindFirstArgs<$Schema, "ScheduledEventInvite">; +export type ScheduledEventInviteExistsArgs = $ExistsArgs<$Schema, "ScheduledEventInvite">; export type ScheduledEventInviteCreateArgs = $CreateArgs<$Schema, "ScheduledEventInvite">; export type ScheduledEventInviteCreateManyArgs = $CreateManyArgs<$Schema, "ScheduledEventInvite">; export type ScheduledEventInviteCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ScheduledEventInvite">; @@ -411,6 +431,7 @@ export type ScheduledEventInviteGetPayload; export type CredentialFindUniqueArgs = $FindUniqueArgs<$Schema, "Credential">; export type CredentialFindFirstArgs = $FindFirstArgs<$Schema, "Credential">; +export type CredentialExistsArgs = $ExistsArgs<$Schema, "Credential">; export type CredentialCreateArgs = $CreateArgs<$Schema, "Credential">; export type CredentialCreateManyArgs = $CreateManyArgs<$Schema, "Credential">; export type CredentialCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Credential">; @@ -431,6 +452,7 @@ export type CredentialGetPayload; export type CalendarConnectionFindUniqueArgs = $FindUniqueArgs<$Schema, "CalendarConnection">; export type CalendarConnectionFindFirstArgs = $FindFirstArgs<$Schema, "CalendarConnection">; +export type CalendarConnectionExistsArgs = $ExistsArgs<$Schema, "CalendarConnection">; export type CalendarConnectionCreateArgs = $CreateArgs<$Schema, "CalendarConnection">; export type CalendarConnectionCreateManyArgs = $CreateManyArgs<$Schema, "CalendarConnection">; export type CalendarConnectionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "CalendarConnection">; @@ -451,6 +473,7 @@ export type CalendarConnectionGetPayload; export type ProviderCalendarFindUniqueArgs = $FindUniqueArgs<$Schema, "ProviderCalendar">; export type ProviderCalendarFindFirstArgs = $FindFirstArgs<$Schema, "ProviderCalendar">; +export type ProviderCalendarExistsArgs = $ExistsArgs<$Schema, "ProviderCalendar">; export type ProviderCalendarCreateArgs = $CreateArgs<$Schema, "ProviderCalendar">; export type ProviderCalendarCreateManyArgs = $CreateManyArgs<$Schema, "ProviderCalendar">; export type ProviderCalendarCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ProviderCalendar">; @@ -471,6 +494,7 @@ export type ProviderCalendarGetPayload; export type InstanceSettingsFindUniqueArgs = $FindUniqueArgs<$Schema, "InstanceSettings">; export type InstanceSettingsFindFirstArgs = $FindFirstArgs<$Schema, "InstanceSettings">; +export type InstanceSettingsExistsArgs = $ExistsArgs<$Schema, "InstanceSettings">; export type InstanceSettingsCreateArgs = $CreateArgs<$Schema, "InstanceSettings">; export type InstanceSettingsCreateManyArgs = $CreateManyArgs<$Schema, "InstanceSettings">; export type InstanceSettingsCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "InstanceSettings">; @@ -491,6 +515,7 @@ export type InstanceSettingsGetPayload; export type LicenseFindUniqueArgs = $FindUniqueArgs<$Schema, "License">; export type LicenseFindFirstArgs = $FindFirstArgs<$Schema, "License">; +export type LicenseExistsArgs = $ExistsArgs<$Schema, "License">; export type LicenseCreateArgs = $CreateArgs<$Schema, "License">; export type LicenseCreateManyArgs = $CreateManyArgs<$Schema, "License">; export type LicenseCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "License">; @@ -511,6 +536,7 @@ export type LicenseGetPayload; export type LicenseValidationFindUniqueArgs = $FindUniqueArgs<$Schema, "LicenseValidation">; export type LicenseValidationFindFirstArgs = $FindFirstArgs<$Schema, "LicenseValidation">; +export type LicenseValidationExistsArgs = $ExistsArgs<$Schema, "LicenseValidation">; export type LicenseValidationCreateArgs = $CreateArgs<$Schema, "LicenseValidation">; export type LicenseValidationCreateManyArgs = $CreateManyArgs<$Schema, "LicenseValidation">; export type LicenseValidationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "LicenseValidation">; @@ -531,6 +557,7 @@ export type LicenseValidationGetPayload; export type InstanceLicenseFindUniqueArgs = $FindUniqueArgs<$Schema, "InstanceLicense">; export type InstanceLicenseFindFirstArgs = $FindFirstArgs<$Schema, "InstanceLicense">; +export type InstanceLicenseExistsArgs = $ExistsArgs<$Schema, "InstanceLicense">; export type InstanceLicenseCreateArgs = $CreateArgs<$Schema, "InstanceLicense">; export type InstanceLicenseCreateManyArgs = $CreateManyArgs<$Schema, "InstanceLicense">; export type InstanceLicenseCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "InstanceLicense">; diff --git a/tests/e2e/github-repos/cal.com/input.ts b/tests/e2e/github-repos/cal.com/input.ts index 85584068b..c5c963cc1 100644 --- a/tests/e2e/github-repos/cal.com/input.ts +++ b/tests/e2e/github-repos/cal.com/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 HostFindManyArgs = $FindManyArgs<$Schema, "Host">; export type HostFindUniqueArgs = $FindUniqueArgs<$Schema, "Host">; export type HostFindFirstArgs = $FindFirstArgs<$Schema, "Host">; +export type HostExistsArgs = $ExistsArgs<$Schema, "Host">; export type HostCreateArgs = $CreateArgs<$Schema, "Host">; export type HostCreateManyArgs = $CreateManyArgs<$Schema, "Host">; export type HostCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Host">; @@ -31,6 +32,7 @@ export type HostGetPayload; export type CalVideoSettingsFindUniqueArgs = $FindUniqueArgs<$Schema, "CalVideoSettings">; export type CalVideoSettingsFindFirstArgs = $FindFirstArgs<$Schema, "CalVideoSettings">; +export type CalVideoSettingsExistsArgs = $ExistsArgs<$Schema, "CalVideoSettings">; export type CalVideoSettingsCreateArgs = $CreateArgs<$Schema, "CalVideoSettings">; export type CalVideoSettingsCreateManyArgs = $CreateManyArgs<$Schema, "CalVideoSettings">; export type CalVideoSettingsCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "CalVideoSettings">; @@ -51,6 +53,7 @@ export type CalVideoSettingsGetPayload; export type EventTypeFindUniqueArgs = $FindUniqueArgs<$Schema, "EventType">; export type EventTypeFindFirstArgs = $FindFirstArgs<$Schema, "EventType">; +export type EventTypeExistsArgs = $ExistsArgs<$Schema, "EventType">; export type EventTypeCreateArgs = $CreateArgs<$Schema, "EventType">; export type EventTypeCreateManyArgs = $CreateManyArgs<$Schema, "EventType">; export type EventTypeCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "EventType">; @@ -71,6 +74,7 @@ export type EventTypeGetPayload; export type CredentialFindUniqueArgs = $FindUniqueArgs<$Schema, "Credential">; export type CredentialFindFirstArgs = $FindFirstArgs<$Schema, "Credential">; +export type CredentialExistsArgs = $ExistsArgs<$Schema, "Credential">; export type CredentialCreateArgs = $CreateArgs<$Schema, "Credential">; export type CredentialCreateManyArgs = $CreateManyArgs<$Schema, "Credential">; export type CredentialCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Credential">; @@ -91,6 +95,7 @@ export type CredentialGetPayload; export type DestinationCalendarFindUniqueArgs = $FindUniqueArgs<$Schema, "DestinationCalendar">; export type DestinationCalendarFindFirstArgs = $FindFirstArgs<$Schema, "DestinationCalendar">; +export type DestinationCalendarExistsArgs = $ExistsArgs<$Schema, "DestinationCalendar">; export type DestinationCalendarCreateArgs = $CreateArgs<$Schema, "DestinationCalendar">; export type DestinationCalendarCreateManyArgs = $CreateManyArgs<$Schema, "DestinationCalendar">; export type DestinationCalendarCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "DestinationCalendar">; @@ -111,6 +116,7 @@ export type DestinationCalendarGetPayload; export type UserPasswordFindUniqueArgs = $FindUniqueArgs<$Schema, "UserPassword">; export type UserPasswordFindFirstArgs = $FindFirstArgs<$Schema, "UserPassword">; +export type UserPasswordExistsArgs = $ExistsArgs<$Schema, "UserPassword">; export type UserPasswordCreateArgs = $CreateArgs<$Schema, "UserPassword">; export type UserPasswordCreateManyArgs = $CreateManyArgs<$Schema, "UserPassword">; export type UserPasswordCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "UserPassword">; @@ -131,6 +137,7 @@ export type UserPasswordGetPayload; export type TravelScheduleFindUniqueArgs = $FindUniqueArgs<$Schema, "TravelSchedule">; export type TravelScheduleFindFirstArgs = $FindFirstArgs<$Schema, "TravelSchedule">; +export type TravelScheduleExistsArgs = $ExistsArgs<$Schema, "TravelSchedule">; export type TravelScheduleCreateArgs = $CreateArgs<$Schema, "TravelSchedule">; export type TravelScheduleCreateManyArgs = $CreateManyArgs<$Schema, "TravelSchedule">; export type TravelScheduleCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TravelSchedule">; @@ -151,6 +158,7 @@ export type TravelScheduleGetPayload; 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">; @@ -171,6 +179,7 @@ export type UserGetPayload; export type NotificationsSubscriptionsFindUniqueArgs = $FindUniqueArgs<$Schema, "NotificationsSubscriptions">; export type NotificationsSubscriptionsFindFirstArgs = $FindFirstArgs<$Schema, "NotificationsSubscriptions">; +export type NotificationsSubscriptionsExistsArgs = $ExistsArgs<$Schema, "NotificationsSubscriptions">; export type NotificationsSubscriptionsCreateArgs = $CreateArgs<$Schema, "NotificationsSubscriptions">; export type NotificationsSubscriptionsCreateManyArgs = $CreateManyArgs<$Schema, "NotificationsSubscriptions">; export type NotificationsSubscriptionsCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "NotificationsSubscriptions">; @@ -191,6 +200,7 @@ export type NotificationsSubscriptionsGetPayload; 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">; @@ -211,6 +221,7 @@ export type ProfileGetPayload; export type TeamFindUniqueArgs = $FindUniqueArgs<$Schema, "Team">; export type TeamFindFirstArgs = $FindFirstArgs<$Schema, "Team">; +export type TeamExistsArgs = $ExistsArgs<$Schema, "Team">; export type TeamCreateArgs = $CreateArgs<$Schema, "Team">; export type TeamCreateManyArgs = $CreateManyArgs<$Schema, "Team">; export type TeamCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Team">; @@ -231,6 +242,7 @@ export type TeamGetPayload; export type CreditBalanceFindUniqueArgs = $FindUniqueArgs<$Schema, "CreditBalance">; export type CreditBalanceFindFirstArgs = $FindFirstArgs<$Schema, "CreditBalance">; +export type CreditBalanceExistsArgs = $ExistsArgs<$Schema, "CreditBalance">; export type CreditBalanceCreateArgs = $CreateArgs<$Schema, "CreditBalance">; export type CreditBalanceCreateManyArgs = $CreateManyArgs<$Schema, "CreditBalance">; export type CreditBalanceCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "CreditBalance">; @@ -251,6 +263,7 @@ export type CreditBalanceGetPayload; export type CreditPurchaseLogFindUniqueArgs = $FindUniqueArgs<$Schema, "CreditPurchaseLog">; export type CreditPurchaseLogFindFirstArgs = $FindFirstArgs<$Schema, "CreditPurchaseLog">; +export type CreditPurchaseLogExistsArgs = $ExistsArgs<$Schema, "CreditPurchaseLog">; export type CreditPurchaseLogCreateArgs = $CreateArgs<$Schema, "CreditPurchaseLog">; export type CreditPurchaseLogCreateManyArgs = $CreateManyArgs<$Schema, "CreditPurchaseLog">; export type CreditPurchaseLogCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "CreditPurchaseLog">; @@ -271,6 +284,7 @@ export type CreditPurchaseLogGetPayload; export type CreditExpenseLogFindUniqueArgs = $FindUniqueArgs<$Schema, "CreditExpenseLog">; export type CreditExpenseLogFindFirstArgs = $FindFirstArgs<$Schema, "CreditExpenseLog">; +export type CreditExpenseLogExistsArgs = $ExistsArgs<$Schema, "CreditExpenseLog">; export type CreditExpenseLogCreateArgs = $CreateArgs<$Schema, "CreditExpenseLog">; export type CreditExpenseLogCreateManyArgs = $CreateManyArgs<$Schema, "CreditExpenseLog">; export type CreditExpenseLogCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "CreditExpenseLog">; @@ -291,6 +305,7 @@ export type CreditExpenseLogGetPayload; export type OrganizationSettingsFindUniqueArgs = $FindUniqueArgs<$Schema, "OrganizationSettings">; export type OrganizationSettingsFindFirstArgs = $FindFirstArgs<$Schema, "OrganizationSettings">; +export type OrganizationSettingsExistsArgs = $ExistsArgs<$Schema, "OrganizationSettings">; export type OrganizationSettingsCreateArgs = $CreateArgs<$Schema, "OrganizationSettings">; export type OrganizationSettingsCreateManyArgs = $CreateManyArgs<$Schema, "OrganizationSettings">; export type OrganizationSettingsCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "OrganizationSettings">; @@ -311,6 +326,7 @@ export type OrganizationSettingsGetPayload; export type MembershipFindUniqueArgs = $FindUniqueArgs<$Schema, "Membership">; export type MembershipFindFirstArgs = $FindFirstArgs<$Schema, "Membership">; +export type MembershipExistsArgs = $ExistsArgs<$Schema, "Membership">; export type MembershipCreateArgs = $CreateArgs<$Schema, "Membership">; export type MembershipCreateManyArgs = $CreateManyArgs<$Schema, "Membership">; export type MembershipCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Membership">; @@ -331,6 +347,7 @@ export type MembershipGetPayload; export type VerificationTokenFindUniqueArgs = $FindUniqueArgs<$Schema, "VerificationToken">; export type VerificationTokenFindFirstArgs = $FindFirstArgs<$Schema, "VerificationToken">; +export type VerificationTokenExistsArgs = $ExistsArgs<$Schema, "VerificationToken">; export type VerificationTokenCreateArgs = $CreateArgs<$Schema, "VerificationToken">; export type VerificationTokenCreateManyArgs = $CreateManyArgs<$Schema, "VerificationToken">; export type VerificationTokenCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "VerificationToken">; @@ -351,6 +368,7 @@ export type VerificationTokenGetPayload; export type InstantMeetingTokenFindUniqueArgs = $FindUniqueArgs<$Schema, "InstantMeetingToken">; export type InstantMeetingTokenFindFirstArgs = $FindFirstArgs<$Schema, "InstantMeetingToken">; +export type InstantMeetingTokenExistsArgs = $ExistsArgs<$Schema, "InstantMeetingToken">; export type InstantMeetingTokenCreateArgs = $CreateArgs<$Schema, "InstantMeetingToken">; export type InstantMeetingTokenCreateManyArgs = $CreateManyArgs<$Schema, "InstantMeetingToken">; export type InstantMeetingTokenCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "InstantMeetingToken">; @@ -371,6 +389,7 @@ export type InstantMeetingTokenGetPayload; export type BookingReferenceFindUniqueArgs = $FindUniqueArgs<$Schema, "BookingReference">; export type BookingReferenceFindFirstArgs = $FindFirstArgs<$Schema, "BookingReference">; +export type BookingReferenceExistsArgs = $ExistsArgs<$Schema, "BookingReference">; export type BookingReferenceCreateArgs = $CreateArgs<$Schema, "BookingReference">; export type BookingReferenceCreateManyArgs = $CreateManyArgs<$Schema, "BookingReference">; export type BookingReferenceCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BookingReference">; @@ -391,6 +410,7 @@ export type BookingReferenceGetPayload; export type AttendeeFindUniqueArgs = $FindUniqueArgs<$Schema, "Attendee">; export type AttendeeFindFirstArgs = $FindFirstArgs<$Schema, "Attendee">; +export type AttendeeExistsArgs = $ExistsArgs<$Schema, "Attendee">; export type AttendeeCreateArgs = $CreateArgs<$Schema, "Attendee">; export type AttendeeCreateManyArgs = $CreateManyArgs<$Schema, "Attendee">; export type AttendeeCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Attendee">; @@ -411,6 +431,7 @@ export type AttendeeGetPayload; export type BookingFindUniqueArgs = $FindUniqueArgs<$Schema, "Booking">; export type BookingFindFirstArgs = $FindFirstArgs<$Schema, "Booking">; +export type BookingExistsArgs = $ExistsArgs<$Schema, "Booking">; export type BookingCreateArgs = $CreateArgs<$Schema, "Booking">; export type BookingCreateManyArgs = $CreateManyArgs<$Schema, "Booking">; export type BookingCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Booking">; @@ -431,6 +452,7 @@ export type BookingGetPayload; export type TrackingFindUniqueArgs = $FindUniqueArgs<$Schema, "Tracking">; export type TrackingFindFirstArgs = $FindFirstArgs<$Schema, "Tracking">; +export type TrackingExistsArgs = $ExistsArgs<$Schema, "Tracking">; export type TrackingCreateArgs = $CreateArgs<$Schema, "Tracking">; export type TrackingCreateManyArgs = $CreateManyArgs<$Schema, "Tracking">; export type TrackingCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Tracking">; @@ -451,6 +473,7 @@ export type TrackingGetPayload; export type ScheduleFindUniqueArgs = $FindUniqueArgs<$Schema, "Schedule">; export type ScheduleFindFirstArgs = $FindFirstArgs<$Schema, "Schedule">; +export type ScheduleExistsArgs = $ExistsArgs<$Schema, "Schedule">; export type ScheduleCreateArgs = $CreateArgs<$Schema, "Schedule">; export type ScheduleCreateManyArgs = $CreateManyArgs<$Schema, "Schedule">; export type ScheduleCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Schedule">; @@ -471,6 +494,7 @@ export type ScheduleGetPayload; export type AvailabilityFindUniqueArgs = $FindUniqueArgs<$Schema, "Availability">; export type AvailabilityFindFirstArgs = $FindFirstArgs<$Schema, "Availability">; +export type AvailabilityExistsArgs = $ExistsArgs<$Schema, "Availability">; export type AvailabilityCreateArgs = $CreateArgs<$Schema, "Availability">; export type AvailabilityCreateManyArgs = $CreateManyArgs<$Schema, "Availability">; export type AvailabilityCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Availability">; @@ -491,6 +515,7 @@ export type AvailabilityGetPayload; export type SelectedCalendarFindUniqueArgs = $FindUniqueArgs<$Schema, "SelectedCalendar">; export type SelectedCalendarFindFirstArgs = $FindFirstArgs<$Schema, "SelectedCalendar">; +export type SelectedCalendarExistsArgs = $ExistsArgs<$Schema, "SelectedCalendar">; export type SelectedCalendarCreateArgs = $CreateArgs<$Schema, "SelectedCalendar">; export type SelectedCalendarCreateManyArgs = $CreateManyArgs<$Schema, "SelectedCalendar">; export type SelectedCalendarCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SelectedCalendar">; @@ -511,6 +536,7 @@ export type SelectedCalendarGetPayload; export type EventTypeCustomInputFindUniqueArgs = $FindUniqueArgs<$Schema, "EventTypeCustomInput">; export type EventTypeCustomInputFindFirstArgs = $FindFirstArgs<$Schema, "EventTypeCustomInput">; +export type EventTypeCustomInputExistsArgs = $ExistsArgs<$Schema, "EventTypeCustomInput">; export type EventTypeCustomInputCreateArgs = $CreateArgs<$Schema, "EventTypeCustomInput">; export type EventTypeCustomInputCreateManyArgs = $CreateManyArgs<$Schema, "EventTypeCustomInput">; export type EventTypeCustomInputCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "EventTypeCustomInput">; @@ -531,6 +557,7 @@ export type EventTypeCustomInputGetPayload; export type ResetPasswordRequestFindUniqueArgs = $FindUniqueArgs<$Schema, "ResetPasswordRequest">; export type ResetPasswordRequestFindFirstArgs = $FindFirstArgs<$Schema, "ResetPasswordRequest">; +export type ResetPasswordRequestExistsArgs = $ExistsArgs<$Schema, "ResetPasswordRequest">; export type ResetPasswordRequestCreateArgs = $CreateArgs<$Schema, "ResetPasswordRequest">; export type ResetPasswordRequestCreateManyArgs = $CreateManyArgs<$Schema, "ResetPasswordRequest">; export type ResetPasswordRequestCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ResetPasswordRequest">; @@ -551,6 +578,7 @@ export type ResetPasswordRequestGetPayload; export type ReminderMailFindUniqueArgs = $FindUniqueArgs<$Schema, "ReminderMail">; export type ReminderMailFindFirstArgs = $FindFirstArgs<$Schema, "ReminderMail">; +export type ReminderMailExistsArgs = $ExistsArgs<$Schema, "ReminderMail">; export type ReminderMailCreateArgs = $CreateArgs<$Schema, "ReminderMail">; export type ReminderMailCreateManyArgs = $CreateManyArgs<$Schema, "ReminderMail">; export type ReminderMailCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ReminderMail">; @@ -571,6 +599,7 @@ export type ReminderMailGetPayload; export type PaymentFindUniqueArgs = $FindUniqueArgs<$Schema, "Payment">; export type PaymentFindFirstArgs = $FindFirstArgs<$Schema, "Payment">; +export type PaymentExistsArgs = $ExistsArgs<$Schema, "Payment">; export type PaymentCreateArgs = $CreateArgs<$Schema, "Payment">; export type PaymentCreateManyArgs = $CreateManyArgs<$Schema, "Payment">; export type PaymentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Payment">; @@ -591,6 +620,7 @@ export type PaymentGetPayload; export type WebhookFindUniqueArgs = $FindUniqueArgs<$Schema, "Webhook">; export type WebhookFindFirstArgs = $FindFirstArgs<$Schema, "Webhook">; +export type WebhookExistsArgs = $ExistsArgs<$Schema, "Webhook">; export type WebhookCreateArgs = $CreateArgs<$Schema, "Webhook">; export type WebhookCreateManyArgs = $CreateManyArgs<$Schema, "Webhook">; export type WebhookCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Webhook">; @@ -611,6 +641,7 @@ export type WebhookGetPayload; export type ImpersonationsFindUniqueArgs = $FindUniqueArgs<$Schema, "Impersonations">; export type ImpersonationsFindFirstArgs = $FindFirstArgs<$Schema, "Impersonations">; +export type ImpersonationsExistsArgs = $ExistsArgs<$Schema, "Impersonations">; export type ImpersonationsCreateArgs = $CreateArgs<$Schema, "Impersonations">; export type ImpersonationsCreateManyArgs = $CreateManyArgs<$Schema, "Impersonations">; export type ImpersonationsCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Impersonations">; @@ -631,6 +662,7 @@ export type ImpersonationsGetPayload; export type ApiKeyFindUniqueArgs = $FindUniqueArgs<$Schema, "ApiKey">; export type ApiKeyFindFirstArgs = $FindFirstArgs<$Schema, "ApiKey">; +export type ApiKeyExistsArgs = $ExistsArgs<$Schema, "ApiKey">; export type ApiKeyCreateArgs = $CreateArgs<$Schema, "ApiKey">; export type ApiKeyCreateManyArgs = $CreateManyArgs<$Schema, "ApiKey">; export type ApiKeyCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ApiKey">; @@ -651,6 +683,7 @@ export type ApiKeyGetPayload; export type RateLimitFindUniqueArgs = $FindUniqueArgs<$Schema, "RateLimit">; export type RateLimitFindFirstArgs = $FindFirstArgs<$Schema, "RateLimit">; +export type RateLimitExistsArgs = $ExistsArgs<$Schema, "RateLimit">; export type RateLimitCreateArgs = $CreateArgs<$Schema, "RateLimit">; export type RateLimitCreateManyArgs = $CreateManyArgs<$Schema, "RateLimit">; export type RateLimitCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RateLimit">; @@ -671,6 +704,7 @@ export type RateLimitGetPayload; export type HashedLinkFindUniqueArgs = $FindUniqueArgs<$Schema, "HashedLink">; export type HashedLinkFindFirstArgs = $FindFirstArgs<$Schema, "HashedLink">; +export type HashedLinkExistsArgs = $ExistsArgs<$Schema, "HashedLink">; export type HashedLinkCreateArgs = $CreateArgs<$Schema, "HashedLink">; export type HashedLinkCreateManyArgs = $CreateManyArgs<$Schema, "HashedLink">; export type HashedLinkCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "HashedLink">; @@ -691,6 +725,7 @@ export type HashedLinkGetPayload; export type AccountFindUniqueArgs = $FindUniqueArgs<$Schema, "Account">; export type AccountFindFirstArgs = $FindFirstArgs<$Schema, "Account">; +export type AccountExistsArgs = $ExistsArgs<$Schema, "Account">; export type AccountCreateArgs = $CreateArgs<$Schema, "Account">; export type AccountCreateManyArgs = $CreateManyArgs<$Schema, "Account">; export type AccountCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Account">; @@ -711,6 +746,7 @@ export type AccountGetPayload; export type SessionFindUniqueArgs = $FindUniqueArgs<$Schema, "Session">; export type SessionFindFirstArgs = $FindFirstArgs<$Schema, "Session">; +export type SessionExistsArgs = $ExistsArgs<$Schema, "Session">; export type SessionCreateArgs = $CreateArgs<$Schema, "Session">; export type SessionCreateManyArgs = $CreateManyArgs<$Schema, "Session">; export type SessionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Session">; @@ -731,6 +767,7 @@ export type SessionGetPayload; export type AppFindUniqueArgs = $FindUniqueArgs<$Schema, "App">; export type AppFindFirstArgs = $FindFirstArgs<$Schema, "App">; +export type AppExistsArgs = $ExistsArgs<$Schema, "App">; export type AppCreateArgs = $CreateArgs<$Schema, "App">; export type AppCreateManyArgs = $CreateManyArgs<$Schema, "App">; export type AppCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "App">; @@ -751,6 +788,7 @@ export type AppGetPayload, export type App_RoutingForms_FormFindManyArgs = $FindManyArgs<$Schema, "App_RoutingForms_Form">; export type App_RoutingForms_FormFindUniqueArgs = $FindUniqueArgs<$Schema, "App_RoutingForms_Form">; export type App_RoutingForms_FormFindFirstArgs = $FindFirstArgs<$Schema, "App_RoutingForms_Form">; +export type App_RoutingForms_FormExistsArgs = $ExistsArgs<$Schema, "App_RoutingForms_Form">; export type App_RoutingForms_FormCreateArgs = $CreateArgs<$Schema, "App_RoutingForms_Form">; export type App_RoutingForms_FormCreateManyArgs = $CreateManyArgs<$Schema, "App_RoutingForms_Form">; export type App_RoutingForms_FormCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "App_RoutingForms_Form">; @@ -771,6 +809,7 @@ export type App_RoutingForms_FormGetPayload; export type App_RoutingForms_FormResponseFindUniqueArgs = $FindUniqueArgs<$Schema, "App_RoutingForms_FormResponse">; export type App_RoutingForms_FormResponseFindFirstArgs = $FindFirstArgs<$Schema, "App_RoutingForms_FormResponse">; +export type App_RoutingForms_FormResponseExistsArgs = $ExistsArgs<$Schema, "App_RoutingForms_FormResponse">; export type App_RoutingForms_FormResponseCreateArgs = $CreateArgs<$Schema, "App_RoutingForms_FormResponse">; export type App_RoutingForms_FormResponseCreateManyArgs = $CreateManyArgs<$Schema, "App_RoutingForms_FormResponse">; export type App_RoutingForms_FormResponseCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "App_RoutingForms_FormResponse">; @@ -791,6 +830,7 @@ export type App_RoutingForms_FormResponseGetPayload; export type App_RoutingForms_QueuedFormResponseFindUniqueArgs = $FindUniqueArgs<$Schema, "App_RoutingForms_QueuedFormResponse">; export type App_RoutingForms_QueuedFormResponseFindFirstArgs = $FindFirstArgs<$Schema, "App_RoutingForms_QueuedFormResponse">; +export type App_RoutingForms_QueuedFormResponseExistsArgs = $ExistsArgs<$Schema, "App_RoutingForms_QueuedFormResponse">; export type App_RoutingForms_QueuedFormResponseCreateArgs = $CreateArgs<$Schema, "App_RoutingForms_QueuedFormResponse">; export type App_RoutingForms_QueuedFormResponseCreateManyArgs = $CreateManyArgs<$Schema, "App_RoutingForms_QueuedFormResponse">; export type App_RoutingForms_QueuedFormResponseCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "App_RoutingForms_QueuedFormResponse">; @@ -811,6 +851,7 @@ export type App_RoutingForms_QueuedFormResponseGetPayload; export type RoutingFormResponseFieldFindUniqueArgs = $FindUniqueArgs<$Schema, "RoutingFormResponseField">; export type RoutingFormResponseFieldFindFirstArgs = $FindFirstArgs<$Schema, "RoutingFormResponseField">; +export type RoutingFormResponseFieldExistsArgs = $ExistsArgs<$Schema, "RoutingFormResponseField">; export type RoutingFormResponseFieldCreateArgs = $CreateArgs<$Schema, "RoutingFormResponseField">; export type RoutingFormResponseFieldCreateManyArgs = $CreateManyArgs<$Schema, "RoutingFormResponseField">; export type RoutingFormResponseFieldCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RoutingFormResponseField">; @@ -831,6 +872,7 @@ export type RoutingFormResponseFieldGetPayload; export type RoutingFormResponseFindUniqueArgs = $FindUniqueArgs<$Schema, "RoutingFormResponse">; export type RoutingFormResponseFindFirstArgs = $FindFirstArgs<$Schema, "RoutingFormResponse">; +export type RoutingFormResponseExistsArgs = $ExistsArgs<$Schema, "RoutingFormResponse">; export type RoutingFormResponseCreateArgs = $CreateArgs<$Schema, "RoutingFormResponse">; export type RoutingFormResponseCreateManyArgs = $CreateManyArgs<$Schema, "RoutingFormResponse">; export type RoutingFormResponseCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RoutingFormResponse">; @@ -851,6 +893,7 @@ export type RoutingFormResponseGetPayload; export type RoutingFormResponseDenormalizedFindUniqueArgs = $FindUniqueArgs<$Schema, "RoutingFormResponseDenormalized">; export type RoutingFormResponseDenormalizedFindFirstArgs = $FindFirstArgs<$Schema, "RoutingFormResponseDenormalized">; +export type RoutingFormResponseDenormalizedExistsArgs = $ExistsArgs<$Schema, "RoutingFormResponseDenormalized">; export type RoutingFormResponseDenormalizedCreateArgs = $CreateArgs<$Schema, "RoutingFormResponseDenormalized">; export type RoutingFormResponseDenormalizedCreateManyArgs = $CreateManyArgs<$Schema, "RoutingFormResponseDenormalized">; export type RoutingFormResponseDenormalizedCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RoutingFormResponseDenormalized">; @@ -871,6 +914,7 @@ export type RoutingFormResponseDenormalizedGetPayload; export type FeedbackFindUniqueArgs = $FindUniqueArgs<$Schema, "Feedback">; export type FeedbackFindFirstArgs = $FindFirstArgs<$Schema, "Feedback">; +export type FeedbackExistsArgs = $ExistsArgs<$Schema, "Feedback">; export type FeedbackCreateArgs = $CreateArgs<$Schema, "Feedback">; export type FeedbackCreateManyArgs = $CreateManyArgs<$Schema, "Feedback">; export type FeedbackCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Feedback">; @@ -891,6 +935,7 @@ export type FeedbackGetPayload; export type WorkflowStepFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkflowStep">; export type WorkflowStepFindFirstArgs = $FindFirstArgs<$Schema, "WorkflowStep">; +export type WorkflowStepExistsArgs = $ExistsArgs<$Schema, "WorkflowStep">; export type WorkflowStepCreateArgs = $CreateArgs<$Schema, "WorkflowStep">; export type WorkflowStepCreateManyArgs = $CreateManyArgs<$Schema, "WorkflowStep">; export type WorkflowStepCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkflowStep">; @@ -911,6 +956,7 @@ export type WorkflowStepGetPayload; export type WorkflowFindUniqueArgs = $FindUniqueArgs<$Schema, "Workflow">; export type WorkflowFindFirstArgs = $FindFirstArgs<$Schema, "Workflow">; +export type WorkflowExistsArgs = $ExistsArgs<$Schema, "Workflow">; export type WorkflowCreateArgs = $CreateArgs<$Schema, "Workflow">; export type WorkflowCreateManyArgs = $CreateManyArgs<$Schema, "Workflow">; export type WorkflowCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Workflow">; @@ -931,6 +977,7 @@ export type WorkflowGetPayload; export type AIPhoneCallConfigurationFindUniqueArgs = $FindUniqueArgs<$Schema, "AIPhoneCallConfiguration">; export type AIPhoneCallConfigurationFindFirstArgs = $FindFirstArgs<$Schema, "AIPhoneCallConfiguration">; +export type AIPhoneCallConfigurationExistsArgs = $ExistsArgs<$Schema, "AIPhoneCallConfiguration">; export type AIPhoneCallConfigurationCreateArgs = $CreateArgs<$Schema, "AIPhoneCallConfiguration">; export type AIPhoneCallConfigurationCreateManyArgs = $CreateManyArgs<$Schema, "AIPhoneCallConfiguration">; export type AIPhoneCallConfigurationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "AIPhoneCallConfiguration">; @@ -951,6 +998,7 @@ export type AIPhoneCallConfigurationGetPayload; export type WorkflowsOnEventTypesFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkflowsOnEventTypes">; export type WorkflowsOnEventTypesFindFirstArgs = $FindFirstArgs<$Schema, "WorkflowsOnEventTypes">; +export type WorkflowsOnEventTypesExistsArgs = $ExistsArgs<$Schema, "WorkflowsOnEventTypes">; export type WorkflowsOnEventTypesCreateArgs = $CreateArgs<$Schema, "WorkflowsOnEventTypes">; export type WorkflowsOnEventTypesCreateManyArgs = $CreateManyArgs<$Schema, "WorkflowsOnEventTypes">; export type WorkflowsOnEventTypesCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkflowsOnEventTypes">; @@ -971,6 +1019,7 @@ export type WorkflowsOnEventTypesGetPayload; export type WorkflowsOnTeamsFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkflowsOnTeams">; export type WorkflowsOnTeamsFindFirstArgs = $FindFirstArgs<$Schema, "WorkflowsOnTeams">; +export type WorkflowsOnTeamsExistsArgs = $ExistsArgs<$Schema, "WorkflowsOnTeams">; export type WorkflowsOnTeamsCreateArgs = $CreateArgs<$Schema, "WorkflowsOnTeams">; export type WorkflowsOnTeamsCreateManyArgs = $CreateManyArgs<$Schema, "WorkflowsOnTeams">; export type WorkflowsOnTeamsCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkflowsOnTeams">; @@ -991,6 +1040,7 @@ export type WorkflowsOnTeamsGetPayload; export type DeploymentFindUniqueArgs = $FindUniqueArgs<$Schema, "Deployment">; export type DeploymentFindFirstArgs = $FindFirstArgs<$Schema, "Deployment">; +export type DeploymentExistsArgs = $ExistsArgs<$Schema, "Deployment">; export type DeploymentCreateArgs = $CreateArgs<$Schema, "Deployment">; export type DeploymentCreateManyArgs = $CreateManyArgs<$Schema, "Deployment">; export type DeploymentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Deployment">; @@ -1011,6 +1061,7 @@ export type DeploymentGetPayload; export type WorkflowReminderFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkflowReminder">; export type WorkflowReminderFindFirstArgs = $FindFirstArgs<$Schema, "WorkflowReminder">; +export type WorkflowReminderExistsArgs = $ExistsArgs<$Schema, "WorkflowReminder">; export type WorkflowReminderCreateArgs = $CreateArgs<$Schema, "WorkflowReminder">; export type WorkflowReminderCreateManyArgs = $CreateManyArgs<$Schema, "WorkflowReminder">; export type WorkflowReminderCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkflowReminder">; @@ -1031,6 +1082,7 @@ export type WorkflowReminderGetPayload; export type WebhookScheduledTriggersFindUniqueArgs = $FindUniqueArgs<$Schema, "WebhookScheduledTriggers">; export type WebhookScheduledTriggersFindFirstArgs = $FindFirstArgs<$Schema, "WebhookScheduledTriggers">; +export type WebhookScheduledTriggersExistsArgs = $ExistsArgs<$Schema, "WebhookScheduledTriggers">; export type WebhookScheduledTriggersCreateArgs = $CreateArgs<$Schema, "WebhookScheduledTriggers">; export type WebhookScheduledTriggersCreateManyArgs = $CreateManyArgs<$Schema, "WebhookScheduledTriggers">; export type WebhookScheduledTriggersCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WebhookScheduledTriggers">; @@ -1051,6 +1103,7 @@ export type WebhookScheduledTriggersGetPayload; export type BookingSeatFindUniqueArgs = $FindUniqueArgs<$Schema, "BookingSeat">; export type BookingSeatFindFirstArgs = $FindFirstArgs<$Schema, "BookingSeat">; +export type BookingSeatExistsArgs = $ExistsArgs<$Schema, "BookingSeat">; export type BookingSeatCreateArgs = $CreateArgs<$Schema, "BookingSeat">; export type BookingSeatCreateManyArgs = $CreateManyArgs<$Schema, "BookingSeat">; export type BookingSeatCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BookingSeat">; @@ -1071,6 +1124,7 @@ export type BookingSeatGetPayload; export type VerifiedNumberFindUniqueArgs = $FindUniqueArgs<$Schema, "VerifiedNumber">; export type VerifiedNumberFindFirstArgs = $FindFirstArgs<$Schema, "VerifiedNumber">; +export type VerifiedNumberExistsArgs = $ExistsArgs<$Schema, "VerifiedNumber">; export type VerifiedNumberCreateArgs = $CreateArgs<$Schema, "VerifiedNumber">; export type VerifiedNumberCreateManyArgs = $CreateManyArgs<$Schema, "VerifiedNumber">; export type VerifiedNumberCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "VerifiedNumber">; @@ -1091,6 +1145,7 @@ export type VerifiedNumberGetPayload; export type VerifiedEmailFindUniqueArgs = $FindUniqueArgs<$Schema, "VerifiedEmail">; export type VerifiedEmailFindFirstArgs = $FindFirstArgs<$Schema, "VerifiedEmail">; +export type VerifiedEmailExistsArgs = $ExistsArgs<$Schema, "VerifiedEmail">; export type VerifiedEmailCreateArgs = $CreateArgs<$Schema, "VerifiedEmail">; export type VerifiedEmailCreateManyArgs = $CreateManyArgs<$Schema, "VerifiedEmail">; export type VerifiedEmailCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "VerifiedEmail">; @@ -1111,6 +1166,7 @@ export type VerifiedEmailGetPayload; export type FeatureFindUniqueArgs = $FindUniqueArgs<$Schema, "Feature">; export type FeatureFindFirstArgs = $FindFirstArgs<$Schema, "Feature">; +export type FeatureExistsArgs = $ExistsArgs<$Schema, "Feature">; export type FeatureCreateArgs = $CreateArgs<$Schema, "Feature">; export type FeatureCreateManyArgs = $CreateManyArgs<$Schema, "Feature">; export type FeatureCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Feature">; @@ -1131,6 +1187,7 @@ export type FeatureGetPayload; export type UserFeaturesFindUniqueArgs = $FindUniqueArgs<$Schema, "UserFeatures">; export type UserFeaturesFindFirstArgs = $FindFirstArgs<$Schema, "UserFeatures">; +export type UserFeaturesExistsArgs = $ExistsArgs<$Schema, "UserFeatures">; export type UserFeaturesCreateArgs = $CreateArgs<$Schema, "UserFeatures">; export type UserFeaturesCreateManyArgs = $CreateManyArgs<$Schema, "UserFeatures">; export type UserFeaturesCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "UserFeatures">; @@ -1151,6 +1208,7 @@ export type UserFeaturesGetPayload; export type TeamFeaturesFindUniqueArgs = $FindUniqueArgs<$Schema, "TeamFeatures">; export type TeamFeaturesFindFirstArgs = $FindFirstArgs<$Schema, "TeamFeatures">; +export type TeamFeaturesExistsArgs = $ExistsArgs<$Schema, "TeamFeatures">; export type TeamFeaturesCreateArgs = $CreateArgs<$Schema, "TeamFeatures">; export type TeamFeaturesCreateManyArgs = $CreateManyArgs<$Schema, "TeamFeatures">; export type TeamFeaturesCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TeamFeatures">; @@ -1171,6 +1229,7 @@ export type TeamFeaturesGetPayload; export type SelectedSlotsFindUniqueArgs = $FindUniqueArgs<$Schema, "SelectedSlots">; export type SelectedSlotsFindFirstArgs = $FindFirstArgs<$Schema, "SelectedSlots">; +export type SelectedSlotsExistsArgs = $ExistsArgs<$Schema, "SelectedSlots">; export type SelectedSlotsCreateArgs = $CreateArgs<$Schema, "SelectedSlots">; export type SelectedSlotsCreateManyArgs = $CreateManyArgs<$Schema, "SelectedSlots">; export type SelectedSlotsCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SelectedSlots">; @@ -1191,6 +1250,7 @@ export type SelectedSlotsGetPayload; export type OAuthClientFindUniqueArgs = $FindUniqueArgs<$Schema, "OAuthClient">; export type OAuthClientFindFirstArgs = $FindFirstArgs<$Schema, "OAuthClient">; +export type OAuthClientExistsArgs = $ExistsArgs<$Schema, "OAuthClient">; export type OAuthClientCreateArgs = $CreateArgs<$Schema, "OAuthClient">; export type OAuthClientCreateManyArgs = $CreateManyArgs<$Schema, "OAuthClient">; export type OAuthClientCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "OAuthClient">; @@ -1211,6 +1271,7 @@ export type OAuthClientGetPayload; export type AccessCodeFindUniqueArgs = $FindUniqueArgs<$Schema, "AccessCode">; export type AccessCodeFindFirstArgs = $FindFirstArgs<$Schema, "AccessCode">; +export type AccessCodeExistsArgs = $ExistsArgs<$Schema, "AccessCode">; export type AccessCodeCreateArgs = $CreateArgs<$Schema, "AccessCode">; export type AccessCodeCreateManyArgs = $CreateManyArgs<$Schema, "AccessCode">; export type AccessCodeCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "AccessCode">; @@ -1231,6 +1292,7 @@ export type AccessCodeGetPayload; export type BookingTimeStatusFindUniqueArgs = $FindUniqueArgs<$Schema, "BookingTimeStatus">; export type BookingTimeStatusFindFirstArgs = $FindFirstArgs<$Schema, "BookingTimeStatus">; +export type BookingTimeStatusExistsArgs = $ExistsArgs<$Schema, "BookingTimeStatus">; export type BookingTimeStatusCreateArgs = $CreateArgs<$Schema, "BookingTimeStatus">; export type BookingTimeStatusCreateManyArgs = $CreateManyArgs<$Schema, "BookingTimeStatus">; export type BookingTimeStatusCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BookingTimeStatus">; @@ -1251,6 +1313,7 @@ export type BookingTimeStatusGetPayload; export type BookingDenormalizedFindUniqueArgs = $FindUniqueArgs<$Schema, "BookingDenormalized">; export type BookingDenormalizedFindFirstArgs = $FindFirstArgs<$Schema, "BookingDenormalized">; +export type BookingDenormalizedExistsArgs = $ExistsArgs<$Schema, "BookingDenormalized">; export type BookingDenormalizedCreateArgs = $CreateArgs<$Schema, "BookingDenormalized">; export type BookingDenormalizedCreateManyArgs = $CreateManyArgs<$Schema, "BookingDenormalized">; export type BookingDenormalizedCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BookingDenormalized">; @@ -1271,6 +1334,7 @@ export type BookingDenormalizedGetPayload; export type BookingTimeStatusDenormalizedFindUniqueArgs = $FindUniqueArgs<$Schema, "BookingTimeStatusDenormalized">; export type BookingTimeStatusDenormalizedFindFirstArgs = $FindFirstArgs<$Schema, "BookingTimeStatusDenormalized">; +export type BookingTimeStatusDenormalizedExistsArgs = $ExistsArgs<$Schema, "BookingTimeStatusDenormalized">; export type BookingTimeStatusDenormalizedCreateArgs = $CreateArgs<$Schema, "BookingTimeStatusDenormalized">; export type BookingTimeStatusDenormalizedCreateManyArgs = $CreateManyArgs<$Schema, "BookingTimeStatusDenormalized">; export type BookingTimeStatusDenormalizedCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BookingTimeStatusDenormalized">; @@ -1291,6 +1355,7 @@ export type BookingTimeStatusDenormalizedGetPayload; export type CalendarCacheFindUniqueArgs = $FindUniqueArgs<$Schema, "CalendarCache">; export type CalendarCacheFindFirstArgs = $FindFirstArgs<$Schema, "CalendarCache">; +export type CalendarCacheExistsArgs = $ExistsArgs<$Schema, "CalendarCache">; export type CalendarCacheCreateArgs = $CreateArgs<$Schema, "CalendarCache">; export type CalendarCacheCreateManyArgs = $CreateManyArgs<$Schema, "CalendarCache">; export type CalendarCacheCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "CalendarCache">; @@ -1311,6 +1376,7 @@ export type CalendarCacheGetPayload; export type TempOrgRedirectFindUniqueArgs = $FindUniqueArgs<$Schema, "TempOrgRedirect">; export type TempOrgRedirectFindFirstArgs = $FindFirstArgs<$Schema, "TempOrgRedirect">; +export type TempOrgRedirectExistsArgs = $ExistsArgs<$Schema, "TempOrgRedirect">; export type TempOrgRedirectCreateArgs = $CreateArgs<$Schema, "TempOrgRedirect">; export type TempOrgRedirectCreateManyArgs = $CreateManyArgs<$Schema, "TempOrgRedirect">; export type TempOrgRedirectCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TempOrgRedirect">; @@ -1331,6 +1397,7 @@ export type TempOrgRedirectGetPayload; export type AvatarFindUniqueArgs = $FindUniqueArgs<$Schema, "Avatar">; export type AvatarFindFirstArgs = $FindFirstArgs<$Schema, "Avatar">; +export type AvatarExistsArgs = $ExistsArgs<$Schema, "Avatar">; export type AvatarCreateArgs = $CreateArgs<$Schema, "Avatar">; export type AvatarCreateManyArgs = $CreateManyArgs<$Schema, "Avatar">; export type AvatarCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Avatar">; @@ -1351,6 +1418,7 @@ export type AvatarGetPayload; export type OutOfOfficeEntryFindUniqueArgs = $FindUniqueArgs<$Schema, "OutOfOfficeEntry">; export type OutOfOfficeEntryFindFirstArgs = $FindFirstArgs<$Schema, "OutOfOfficeEntry">; +export type OutOfOfficeEntryExistsArgs = $ExistsArgs<$Schema, "OutOfOfficeEntry">; export type OutOfOfficeEntryCreateArgs = $CreateArgs<$Schema, "OutOfOfficeEntry">; export type OutOfOfficeEntryCreateManyArgs = $CreateManyArgs<$Schema, "OutOfOfficeEntry">; export type OutOfOfficeEntryCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "OutOfOfficeEntry">; @@ -1371,6 +1439,7 @@ export type OutOfOfficeEntryGetPayload; export type OutOfOfficeReasonFindUniqueArgs = $FindUniqueArgs<$Schema, "OutOfOfficeReason">; export type OutOfOfficeReasonFindFirstArgs = $FindFirstArgs<$Schema, "OutOfOfficeReason">; +export type OutOfOfficeReasonExistsArgs = $ExistsArgs<$Schema, "OutOfOfficeReason">; export type OutOfOfficeReasonCreateArgs = $CreateArgs<$Schema, "OutOfOfficeReason">; export type OutOfOfficeReasonCreateManyArgs = $CreateManyArgs<$Schema, "OutOfOfficeReason">; export type OutOfOfficeReasonCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "OutOfOfficeReason">; @@ -1391,6 +1460,7 @@ export type OutOfOfficeReasonGetPayload; export type PlatformOAuthClientFindUniqueArgs = $FindUniqueArgs<$Schema, "PlatformOAuthClient">; export type PlatformOAuthClientFindFirstArgs = $FindFirstArgs<$Schema, "PlatformOAuthClient">; +export type PlatformOAuthClientExistsArgs = $ExistsArgs<$Schema, "PlatformOAuthClient">; export type PlatformOAuthClientCreateArgs = $CreateArgs<$Schema, "PlatformOAuthClient">; export type PlatformOAuthClientCreateManyArgs = $CreateManyArgs<$Schema, "PlatformOAuthClient">; export type PlatformOAuthClientCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "PlatformOAuthClient">; @@ -1411,6 +1481,7 @@ export type PlatformOAuthClientGetPayload; export type PlatformAuthorizationTokenFindUniqueArgs = $FindUniqueArgs<$Schema, "PlatformAuthorizationToken">; export type PlatformAuthorizationTokenFindFirstArgs = $FindFirstArgs<$Schema, "PlatformAuthorizationToken">; +export type PlatformAuthorizationTokenExistsArgs = $ExistsArgs<$Schema, "PlatformAuthorizationToken">; export type PlatformAuthorizationTokenCreateArgs = $CreateArgs<$Schema, "PlatformAuthorizationToken">; export type PlatformAuthorizationTokenCreateManyArgs = $CreateManyArgs<$Schema, "PlatformAuthorizationToken">; export type PlatformAuthorizationTokenCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "PlatformAuthorizationToken">; @@ -1431,6 +1502,7 @@ export type PlatformAuthorizationTokenGetPayload; export type AccessTokenFindUniqueArgs = $FindUniqueArgs<$Schema, "AccessToken">; export type AccessTokenFindFirstArgs = $FindFirstArgs<$Schema, "AccessToken">; +export type AccessTokenExistsArgs = $ExistsArgs<$Schema, "AccessToken">; export type AccessTokenCreateArgs = $CreateArgs<$Schema, "AccessToken">; export type AccessTokenCreateManyArgs = $CreateManyArgs<$Schema, "AccessToken">; export type AccessTokenCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "AccessToken">; @@ -1451,6 +1523,7 @@ export type AccessTokenGetPayload; export type RefreshTokenFindUniqueArgs = $FindUniqueArgs<$Schema, "RefreshToken">; export type RefreshTokenFindFirstArgs = $FindFirstArgs<$Schema, "RefreshToken">; +export type RefreshTokenExistsArgs = $ExistsArgs<$Schema, "RefreshToken">; export type RefreshTokenCreateArgs = $CreateArgs<$Schema, "RefreshToken">; export type RefreshTokenCreateManyArgs = $CreateManyArgs<$Schema, "RefreshToken">; export type RefreshTokenCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RefreshToken">; @@ -1471,6 +1544,7 @@ export type RefreshTokenGetPayload; export type DSyncDataFindUniqueArgs = $FindUniqueArgs<$Schema, "DSyncData">; export type DSyncDataFindFirstArgs = $FindFirstArgs<$Schema, "DSyncData">; +export type DSyncDataExistsArgs = $ExistsArgs<$Schema, "DSyncData">; export type DSyncDataCreateArgs = $CreateArgs<$Schema, "DSyncData">; export type DSyncDataCreateManyArgs = $CreateManyArgs<$Schema, "DSyncData">; export type DSyncDataCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "DSyncData">; @@ -1491,6 +1565,7 @@ export type DSyncDataGetPayload; export type DSyncTeamGroupMappingFindUniqueArgs = $FindUniqueArgs<$Schema, "DSyncTeamGroupMapping">; export type DSyncTeamGroupMappingFindFirstArgs = $FindFirstArgs<$Schema, "DSyncTeamGroupMapping">; +export type DSyncTeamGroupMappingExistsArgs = $ExistsArgs<$Schema, "DSyncTeamGroupMapping">; export type DSyncTeamGroupMappingCreateArgs = $CreateArgs<$Schema, "DSyncTeamGroupMapping">; export type DSyncTeamGroupMappingCreateManyArgs = $CreateManyArgs<$Schema, "DSyncTeamGroupMapping">; export type DSyncTeamGroupMappingCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "DSyncTeamGroupMapping">; @@ -1511,6 +1586,7 @@ export type DSyncTeamGroupMappingGetPayload; export type SecondaryEmailFindUniqueArgs = $FindUniqueArgs<$Schema, "SecondaryEmail">; export type SecondaryEmailFindFirstArgs = $FindFirstArgs<$Schema, "SecondaryEmail">; +export type SecondaryEmailExistsArgs = $ExistsArgs<$Schema, "SecondaryEmail">; export type SecondaryEmailCreateArgs = $CreateArgs<$Schema, "SecondaryEmail">; export type SecondaryEmailCreateManyArgs = $CreateManyArgs<$Schema, "SecondaryEmail">; export type SecondaryEmailCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SecondaryEmail">; @@ -1531,6 +1607,7 @@ export type SecondaryEmailGetPayload; export type TaskFindUniqueArgs = $FindUniqueArgs<$Schema, "Task">; export type TaskFindFirstArgs = $FindFirstArgs<$Schema, "Task">; +export type TaskExistsArgs = $ExistsArgs<$Schema, "Task">; export type TaskCreateArgs = $CreateArgs<$Schema, "Task">; export type TaskCreateManyArgs = $CreateManyArgs<$Schema, "Task">; export type TaskCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Task">; @@ -1551,6 +1628,7 @@ export type TaskGetPayload; export type ManagedOrganizationFindUniqueArgs = $FindUniqueArgs<$Schema, "ManagedOrganization">; export type ManagedOrganizationFindFirstArgs = $FindFirstArgs<$Schema, "ManagedOrganization">; +export type ManagedOrganizationExistsArgs = $ExistsArgs<$Schema, "ManagedOrganization">; export type ManagedOrganizationCreateArgs = $CreateArgs<$Schema, "ManagedOrganization">; export type ManagedOrganizationCreateManyArgs = $CreateManyArgs<$Schema, "ManagedOrganization">; export type ManagedOrganizationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ManagedOrganization">; @@ -1571,6 +1649,7 @@ export type ManagedOrganizationGetPayload; export type PlatformBillingFindUniqueArgs = $FindUniqueArgs<$Schema, "PlatformBilling">; export type PlatformBillingFindFirstArgs = $FindFirstArgs<$Schema, "PlatformBilling">; +export type PlatformBillingExistsArgs = $ExistsArgs<$Schema, "PlatformBilling">; export type PlatformBillingCreateArgs = $CreateArgs<$Schema, "PlatformBilling">; export type PlatformBillingCreateManyArgs = $CreateManyArgs<$Schema, "PlatformBilling">; export type PlatformBillingCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "PlatformBilling">; @@ -1591,6 +1670,7 @@ export type PlatformBillingGetPayload; export type AttributeOptionFindUniqueArgs = $FindUniqueArgs<$Schema, "AttributeOption">; export type AttributeOptionFindFirstArgs = $FindFirstArgs<$Schema, "AttributeOption">; +export type AttributeOptionExistsArgs = $ExistsArgs<$Schema, "AttributeOption">; export type AttributeOptionCreateArgs = $CreateArgs<$Schema, "AttributeOption">; export type AttributeOptionCreateManyArgs = $CreateManyArgs<$Schema, "AttributeOption">; export type AttributeOptionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "AttributeOption">; @@ -1611,6 +1691,7 @@ export type AttributeOptionGetPayload; export type AttributeFindUniqueArgs = $FindUniqueArgs<$Schema, "Attribute">; export type AttributeFindFirstArgs = $FindFirstArgs<$Schema, "Attribute">; +export type AttributeExistsArgs = $ExistsArgs<$Schema, "Attribute">; export type AttributeCreateArgs = $CreateArgs<$Schema, "Attribute">; export type AttributeCreateManyArgs = $CreateManyArgs<$Schema, "Attribute">; export type AttributeCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Attribute">; @@ -1631,6 +1712,7 @@ export type AttributeGetPayload; export type AttributeToUserFindUniqueArgs = $FindUniqueArgs<$Schema, "AttributeToUser">; export type AttributeToUserFindFirstArgs = $FindFirstArgs<$Schema, "AttributeToUser">; +export type AttributeToUserExistsArgs = $ExistsArgs<$Schema, "AttributeToUser">; export type AttributeToUserCreateArgs = $CreateArgs<$Schema, "AttributeToUser">; export type AttributeToUserCreateManyArgs = $CreateManyArgs<$Schema, "AttributeToUser">; export type AttributeToUserCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "AttributeToUser">; @@ -1651,6 +1733,7 @@ export type AttributeToUserGetPayload; export type AssignmentReasonFindUniqueArgs = $FindUniqueArgs<$Schema, "AssignmentReason">; export type AssignmentReasonFindFirstArgs = $FindFirstArgs<$Schema, "AssignmentReason">; +export type AssignmentReasonExistsArgs = $ExistsArgs<$Schema, "AssignmentReason">; export type AssignmentReasonCreateArgs = $CreateArgs<$Schema, "AssignmentReason">; export type AssignmentReasonCreateManyArgs = $CreateManyArgs<$Schema, "AssignmentReason">; export type AssignmentReasonCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "AssignmentReason">; @@ -1671,6 +1754,7 @@ export type AssignmentReasonGetPayload; export type DelegationCredentialFindUniqueArgs = $FindUniqueArgs<$Schema, "DelegationCredential">; export type DelegationCredentialFindFirstArgs = $FindFirstArgs<$Schema, "DelegationCredential">; +export type DelegationCredentialExistsArgs = $ExistsArgs<$Schema, "DelegationCredential">; export type DelegationCredentialCreateArgs = $CreateArgs<$Schema, "DelegationCredential">; export type DelegationCredentialCreateManyArgs = $CreateManyArgs<$Schema, "DelegationCredential">; export type DelegationCredentialCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "DelegationCredential">; @@ -1691,6 +1775,7 @@ export type DelegationCredentialGetPayload; export type DomainWideDelegationFindUniqueArgs = $FindUniqueArgs<$Schema, "DomainWideDelegation">; export type DomainWideDelegationFindFirstArgs = $FindFirstArgs<$Schema, "DomainWideDelegation">; +export type DomainWideDelegationExistsArgs = $ExistsArgs<$Schema, "DomainWideDelegation">; export type DomainWideDelegationCreateArgs = $CreateArgs<$Schema, "DomainWideDelegation">; export type DomainWideDelegationCreateManyArgs = $CreateManyArgs<$Schema, "DomainWideDelegation">; export type DomainWideDelegationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "DomainWideDelegation">; @@ -1711,6 +1796,7 @@ export type DomainWideDelegationGetPayload; export type WorkspacePlatformFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkspacePlatform">; export type WorkspacePlatformFindFirstArgs = $FindFirstArgs<$Schema, "WorkspacePlatform">; +export type WorkspacePlatformExistsArgs = $ExistsArgs<$Schema, "WorkspacePlatform">; export type WorkspacePlatformCreateArgs = $CreateArgs<$Schema, "WorkspacePlatform">; export type WorkspacePlatformCreateManyArgs = $CreateManyArgs<$Schema, "WorkspacePlatform">; export type WorkspacePlatformCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkspacePlatform">; @@ -1731,6 +1817,7 @@ export type WorkspacePlatformGetPayload; export type EventTypeTranslationFindUniqueArgs = $FindUniqueArgs<$Schema, "EventTypeTranslation">; export type EventTypeTranslationFindFirstArgs = $FindFirstArgs<$Schema, "EventTypeTranslation">; +export type EventTypeTranslationExistsArgs = $ExistsArgs<$Schema, "EventTypeTranslation">; export type EventTypeTranslationCreateArgs = $CreateArgs<$Schema, "EventTypeTranslation">; export type EventTypeTranslationCreateManyArgs = $CreateManyArgs<$Schema, "EventTypeTranslation">; export type EventTypeTranslationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "EventTypeTranslation">; @@ -1751,6 +1838,7 @@ export type EventTypeTranslationGetPayload; export type WatchlistFindUniqueArgs = $FindUniqueArgs<$Schema, "Watchlist">; export type WatchlistFindFirstArgs = $FindFirstArgs<$Schema, "Watchlist">; +export type WatchlistExistsArgs = $ExistsArgs<$Schema, "Watchlist">; export type WatchlistCreateArgs = $CreateArgs<$Schema, "Watchlist">; export type WatchlistCreateManyArgs = $CreateManyArgs<$Schema, "Watchlist">; export type WatchlistCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Watchlist">; @@ -1771,6 +1859,7 @@ export type WatchlistGetPayload; export type OrganizationOnboardingFindUniqueArgs = $FindUniqueArgs<$Schema, "OrganizationOnboarding">; export type OrganizationOnboardingFindFirstArgs = $FindFirstArgs<$Schema, "OrganizationOnboarding">; +export type OrganizationOnboardingExistsArgs = $ExistsArgs<$Schema, "OrganizationOnboarding">; export type OrganizationOnboardingCreateArgs = $CreateArgs<$Schema, "OrganizationOnboarding">; export type OrganizationOnboardingCreateManyArgs = $CreateManyArgs<$Schema, "OrganizationOnboarding">; export type OrganizationOnboardingCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "OrganizationOnboarding">; @@ -1791,6 +1880,7 @@ export type OrganizationOnboardingGetPayload; export type App_RoutingForms_IncompleteBookingActionsFindUniqueArgs = $FindUniqueArgs<$Schema, "App_RoutingForms_IncompleteBookingActions">; export type App_RoutingForms_IncompleteBookingActionsFindFirstArgs = $FindFirstArgs<$Schema, "App_RoutingForms_IncompleteBookingActions">; +export type App_RoutingForms_IncompleteBookingActionsExistsArgs = $ExistsArgs<$Schema, "App_RoutingForms_IncompleteBookingActions">; export type App_RoutingForms_IncompleteBookingActionsCreateArgs = $CreateArgs<$Schema, "App_RoutingForms_IncompleteBookingActions">; export type App_RoutingForms_IncompleteBookingActionsCreateManyArgs = $CreateManyArgs<$Schema, "App_RoutingForms_IncompleteBookingActions">; export type App_RoutingForms_IncompleteBookingActionsCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "App_RoutingForms_IncompleteBookingActions">; @@ -1811,6 +1901,7 @@ export type App_RoutingForms_IncompleteBookingActionsGetPayload; export type InternalNotePresetFindUniqueArgs = $FindUniqueArgs<$Schema, "InternalNotePreset">; export type InternalNotePresetFindFirstArgs = $FindFirstArgs<$Schema, "InternalNotePreset">; +export type InternalNotePresetExistsArgs = $ExistsArgs<$Schema, "InternalNotePreset">; export type InternalNotePresetCreateArgs = $CreateArgs<$Schema, "InternalNotePreset">; export type InternalNotePresetCreateManyArgs = $CreateManyArgs<$Schema, "InternalNotePreset">; export type InternalNotePresetCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "InternalNotePreset">; @@ -1831,6 +1922,7 @@ export type InternalNotePresetGetPayload; export type FilterSegmentFindUniqueArgs = $FindUniqueArgs<$Schema, "FilterSegment">; export type FilterSegmentFindFirstArgs = $FindFirstArgs<$Schema, "FilterSegment">; +export type FilterSegmentExistsArgs = $ExistsArgs<$Schema, "FilterSegment">; export type FilterSegmentCreateArgs = $CreateArgs<$Schema, "FilterSegment">; export type FilterSegmentCreateManyArgs = $CreateManyArgs<$Schema, "FilterSegment">; export type FilterSegmentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "FilterSegment">; @@ -1851,6 +1943,7 @@ export type FilterSegmentGetPayload; export type UserFilterSegmentPreferenceFindUniqueArgs = $FindUniqueArgs<$Schema, "UserFilterSegmentPreference">; export type UserFilterSegmentPreferenceFindFirstArgs = $FindFirstArgs<$Schema, "UserFilterSegmentPreference">; +export type UserFilterSegmentPreferenceExistsArgs = $ExistsArgs<$Schema, "UserFilterSegmentPreference">; export type UserFilterSegmentPreferenceCreateArgs = $CreateArgs<$Schema, "UserFilterSegmentPreference">; export type UserFilterSegmentPreferenceCreateManyArgs = $CreateManyArgs<$Schema, "UserFilterSegmentPreference">; export type UserFilterSegmentPreferenceCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "UserFilterSegmentPreference">; @@ -1871,6 +1964,7 @@ export type UserFilterSegmentPreferenceGetPayload; export type BookingInternalNoteFindUniqueArgs = $FindUniqueArgs<$Schema, "BookingInternalNote">; export type BookingInternalNoteFindFirstArgs = $FindFirstArgs<$Schema, "BookingInternalNote">; +export type BookingInternalNoteExistsArgs = $ExistsArgs<$Schema, "BookingInternalNote">; export type BookingInternalNoteCreateArgs = $CreateArgs<$Schema, "BookingInternalNote">; export type BookingInternalNoteCreateManyArgs = $CreateManyArgs<$Schema, "BookingInternalNote">; export type BookingInternalNoteCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BookingInternalNote">; @@ -1891,6 +1985,7 @@ export type BookingInternalNoteGetPayload; export type WorkflowOptOutContactFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkflowOptOutContact">; export type WorkflowOptOutContactFindFirstArgs = $FindFirstArgs<$Schema, "WorkflowOptOutContact">; +export type WorkflowOptOutContactExistsArgs = $ExistsArgs<$Schema, "WorkflowOptOutContact">; export type WorkflowOptOutContactCreateArgs = $CreateArgs<$Schema, "WorkflowOptOutContact">; export type WorkflowOptOutContactCreateManyArgs = $CreateManyArgs<$Schema, "WorkflowOptOutContact">; export type WorkflowOptOutContactCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkflowOptOutContact">; @@ -1911,6 +2006,7 @@ export type WorkflowOptOutContactGetPayload; export type RoleFindUniqueArgs = $FindUniqueArgs<$Schema, "Role">; export type RoleFindFirstArgs = $FindFirstArgs<$Schema, "Role">; +export type RoleExistsArgs = $ExistsArgs<$Schema, "Role">; export type RoleCreateArgs = $CreateArgs<$Schema, "Role">; export type RoleCreateManyArgs = $CreateManyArgs<$Schema, "Role">; export type RoleCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Role">; @@ -1931,6 +2027,7 @@ export type RoleGetPayload; export type RolePermissionFindUniqueArgs = $FindUniqueArgs<$Schema, "RolePermission">; export type RolePermissionFindFirstArgs = $FindFirstArgs<$Schema, "RolePermission">; +export type RolePermissionExistsArgs = $ExistsArgs<$Schema, "RolePermission">; export type RolePermissionCreateArgs = $CreateArgs<$Schema, "RolePermission">; export type RolePermissionCreateManyArgs = $CreateManyArgs<$Schema, "RolePermission">; export type RolePermissionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RolePermission">; diff --git a/tests/e2e/github-repos/formbricks/input.ts b/tests/e2e/github-repos/formbricks/input.ts index 5cce0a960..87f2b983b 100644 --- a/tests/e2e/github-repos/formbricks/input.ts +++ b/tests/e2e/github-repos/formbricks/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 WebhookFindManyArgs = $FindManyArgs<$Schema, "Webhook">; export type WebhookFindUniqueArgs = $FindUniqueArgs<$Schema, "Webhook">; export type WebhookFindFirstArgs = $FindFirstArgs<$Schema, "Webhook">; +export type WebhookExistsArgs = $ExistsArgs<$Schema, "Webhook">; export type WebhookCreateArgs = $CreateArgs<$Schema, "Webhook">; export type WebhookCreateManyArgs = $CreateManyArgs<$Schema, "Webhook">; export type WebhookCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Webhook">; @@ -31,6 +32,7 @@ export type WebhookGetPayload; export type ContactAttributeFindUniqueArgs = $FindUniqueArgs<$Schema, "ContactAttribute">; export type ContactAttributeFindFirstArgs = $FindFirstArgs<$Schema, "ContactAttribute">; +export type ContactAttributeExistsArgs = $ExistsArgs<$Schema, "ContactAttribute">; export type ContactAttributeCreateArgs = $CreateArgs<$Schema, "ContactAttribute">; export type ContactAttributeCreateManyArgs = $CreateManyArgs<$Schema, "ContactAttribute">; export type ContactAttributeCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ContactAttribute">; @@ -51,6 +53,7 @@ export type ContactAttributeGetPayload; export type ContactAttributeKeyFindUniqueArgs = $FindUniqueArgs<$Schema, "ContactAttributeKey">; export type ContactAttributeKeyFindFirstArgs = $FindFirstArgs<$Schema, "ContactAttributeKey">; +export type ContactAttributeKeyExistsArgs = $ExistsArgs<$Schema, "ContactAttributeKey">; export type ContactAttributeKeyCreateArgs = $CreateArgs<$Schema, "ContactAttributeKey">; export type ContactAttributeKeyCreateManyArgs = $CreateManyArgs<$Schema, "ContactAttributeKey">; export type ContactAttributeKeyCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ContactAttributeKey">; @@ -71,6 +74,7 @@ export type ContactAttributeKeyGetPayload; export type ContactFindUniqueArgs = $FindUniqueArgs<$Schema, "Contact">; export type ContactFindFirstArgs = $FindFirstArgs<$Schema, "Contact">; +export type ContactExistsArgs = $ExistsArgs<$Schema, "Contact">; export type ContactCreateArgs = $CreateArgs<$Schema, "Contact">; export type ContactCreateManyArgs = $CreateManyArgs<$Schema, "Contact">; export type ContactCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Contact">; @@ -91,6 +95,7 @@ export type ContactGetPayload; export type ResponseFindUniqueArgs = $FindUniqueArgs<$Schema, "Response">; export type ResponseFindFirstArgs = $FindFirstArgs<$Schema, "Response">; +export type ResponseExistsArgs = $ExistsArgs<$Schema, "Response">; export type ResponseCreateArgs = $CreateArgs<$Schema, "Response">; export type ResponseCreateManyArgs = $CreateManyArgs<$Schema, "Response">; export type ResponseCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Response">; @@ -111,6 +116,7 @@ export type ResponseGetPayload; export type ResponseNoteFindUniqueArgs = $FindUniqueArgs<$Schema, "ResponseNote">; export type ResponseNoteFindFirstArgs = $FindFirstArgs<$Schema, "ResponseNote">; +export type ResponseNoteExistsArgs = $ExistsArgs<$Schema, "ResponseNote">; export type ResponseNoteCreateArgs = $CreateArgs<$Schema, "ResponseNote">; export type ResponseNoteCreateManyArgs = $CreateManyArgs<$Schema, "ResponseNote">; export type ResponseNoteCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ResponseNote">; @@ -131,6 +137,7 @@ export type ResponseNoteGetPayload; export type TagFindUniqueArgs = $FindUniqueArgs<$Schema, "Tag">; export type TagFindFirstArgs = $FindFirstArgs<$Schema, "Tag">; +export type TagExistsArgs = $ExistsArgs<$Schema, "Tag">; export type TagCreateArgs = $CreateArgs<$Schema, "Tag">; export type TagCreateManyArgs = $CreateManyArgs<$Schema, "Tag">; export type TagCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Tag">; @@ -151,6 +158,7 @@ export type TagGetPayload, export type TagsOnResponsesFindManyArgs = $FindManyArgs<$Schema, "TagsOnResponses">; export type TagsOnResponsesFindUniqueArgs = $FindUniqueArgs<$Schema, "TagsOnResponses">; export type TagsOnResponsesFindFirstArgs = $FindFirstArgs<$Schema, "TagsOnResponses">; +export type TagsOnResponsesExistsArgs = $ExistsArgs<$Schema, "TagsOnResponses">; export type TagsOnResponsesCreateArgs = $CreateArgs<$Schema, "TagsOnResponses">; export type TagsOnResponsesCreateManyArgs = $CreateManyArgs<$Schema, "TagsOnResponses">; export type TagsOnResponsesCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TagsOnResponses">; @@ -171,6 +179,7 @@ export type TagsOnResponsesGetPayload; export type DisplayFindUniqueArgs = $FindUniqueArgs<$Schema, "Display">; export type DisplayFindFirstArgs = $FindFirstArgs<$Schema, "Display">; +export type DisplayExistsArgs = $ExistsArgs<$Schema, "Display">; export type DisplayCreateArgs = $CreateArgs<$Schema, "Display">; export type DisplayCreateManyArgs = $CreateManyArgs<$Schema, "Display">; export type DisplayCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Display">; @@ -191,6 +200,7 @@ export type DisplayGetPayload; export type SurveyTriggerFindUniqueArgs = $FindUniqueArgs<$Schema, "SurveyTrigger">; export type SurveyTriggerFindFirstArgs = $FindFirstArgs<$Schema, "SurveyTrigger">; +export type SurveyTriggerExistsArgs = $ExistsArgs<$Schema, "SurveyTrigger">; export type SurveyTriggerCreateArgs = $CreateArgs<$Schema, "SurveyTrigger">; export type SurveyTriggerCreateManyArgs = $CreateManyArgs<$Schema, "SurveyTrigger">; export type SurveyTriggerCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SurveyTrigger">; @@ -211,6 +221,7 @@ export type SurveyTriggerGetPayload; export type SurveyAttributeFilterFindUniqueArgs = $FindUniqueArgs<$Schema, "SurveyAttributeFilter">; export type SurveyAttributeFilterFindFirstArgs = $FindFirstArgs<$Schema, "SurveyAttributeFilter">; +export type SurveyAttributeFilterExistsArgs = $ExistsArgs<$Schema, "SurveyAttributeFilter">; export type SurveyAttributeFilterCreateArgs = $CreateArgs<$Schema, "SurveyAttributeFilter">; export type SurveyAttributeFilterCreateManyArgs = $CreateManyArgs<$Schema, "SurveyAttributeFilter">; export type SurveyAttributeFilterCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SurveyAttributeFilter">; @@ -231,6 +242,7 @@ export type SurveyAttributeFilterGetPayload; export type SurveyFindUniqueArgs = $FindUniqueArgs<$Schema, "Survey">; export type SurveyFindFirstArgs = $FindFirstArgs<$Schema, "Survey">; +export type SurveyExistsArgs = $ExistsArgs<$Schema, "Survey">; export type SurveyCreateArgs = $CreateArgs<$Schema, "Survey">; export type SurveyCreateManyArgs = $CreateManyArgs<$Schema, "Survey">; export type SurveyCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Survey">; @@ -251,6 +263,7 @@ export type SurveyGetPayload; export type SurveyFollowUpFindUniqueArgs = $FindUniqueArgs<$Schema, "SurveyFollowUp">; export type SurveyFollowUpFindFirstArgs = $FindFirstArgs<$Schema, "SurveyFollowUp">; +export type SurveyFollowUpExistsArgs = $ExistsArgs<$Schema, "SurveyFollowUp">; export type SurveyFollowUpCreateArgs = $CreateArgs<$Schema, "SurveyFollowUp">; export type SurveyFollowUpCreateManyArgs = $CreateManyArgs<$Schema, "SurveyFollowUp">; export type SurveyFollowUpCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SurveyFollowUp">; @@ -271,6 +284,7 @@ export type SurveyFollowUpGetPayload; export type ActionClassFindUniqueArgs = $FindUniqueArgs<$Schema, "ActionClass">; export type ActionClassFindFirstArgs = $FindFirstArgs<$Schema, "ActionClass">; +export type ActionClassExistsArgs = $ExistsArgs<$Schema, "ActionClass">; export type ActionClassCreateArgs = $CreateArgs<$Schema, "ActionClass">; export type ActionClassCreateManyArgs = $CreateManyArgs<$Schema, "ActionClass">; export type ActionClassCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ActionClass">; @@ -291,6 +305,7 @@ export type ActionClassGetPayload; export type IntegrationFindUniqueArgs = $FindUniqueArgs<$Schema, "Integration">; export type IntegrationFindFirstArgs = $FindFirstArgs<$Schema, "Integration">; +export type IntegrationExistsArgs = $ExistsArgs<$Schema, "Integration">; export type IntegrationCreateArgs = $CreateArgs<$Schema, "Integration">; export type IntegrationCreateManyArgs = $CreateManyArgs<$Schema, "Integration">; export type IntegrationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Integration">; @@ -311,6 +326,7 @@ export type IntegrationGetPayload; export type DataMigrationFindUniqueArgs = $FindUniqueArgs<$Schema, "DataMigration">; export type DataMigrationFindFirstArgs = $FindFirstArgs<$Schema, "DataMigration">; +export type DataMigrationExistsArgs = $ExistsArgs<$Schema, "DataMigration">; export type DataMigrationCreateArgs = $CreateArgs<$Schema, "DataMigration">; export type DataMigrationCreateManyArgs = $CreateManyArgs<$Schema, "DataMigration">; export type DataMigrationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "DataMigration">; @@ -331,6 +347,7 @@ export type DataMigrationGetPayload; export type EnvironmentFindUniqueArgs = $FindUniqueArgs<$Schema, "Environment">; export type EnvironmentFindFirstArgs = $FindFirstArgs<$Schema, "Environment">; +export type EnvironmentExistsArgs = $ExistsArgs<$Schema, "Environment">; export type EnvironmentCreateArgs = $CreateArgs<$Schema, "Environment">; export type EnvironmentCreateManyArgs = $CreateManyArgs<$Schema, "Environment">; export type EnvironmentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Environment">; @@ -351,6 +368,7 @@ export type EnvironmentGetPayload; export type ProjectFindUniqueArgs = $FindUniqueArgs<$Schema, "Project">; export type ProjectFindFirstArgs = $FindFirstArgs<$Schema, "Project">; +export type ProjectExistsArgs = $ExistsArgs<$Schema, "Project">; export type ProjectCreateArgs = $CreateArgs<$Schema, "Project">; export type ProjectCreateManyArgs = $CreateManyArgs<$Schema, "Project">; export type ProjectCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Project">; @@ -371,6 +389,7 @@ export type ProjectGetPayload; export type OrganizationFindUniqueArgs = $FindUniqueArgs<$Schema, "Organization">; export type OrganizationFindFirstArgs = $FindFirstArgs<$Schema, "Organization">; +export type OrganizationExistsArgs = $ExistsArgs<$Schema, "Organization">; export type OrganizationCreateArgs = $CreateArgs<$Schema, "Organization">; export type OrganizationCreateManyArgs = $CreateManyArgs<$Schema, "Organization">; export type OrganizationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Organization">; @@ -391,6 +410,7 @@ export type OrganizationGetPayload; export type MembershipFindUniqueArgs = $FindUniqueArgs<$Schema, "Membership">; export type MembershipFindFirstArgs = $FindFirstArgs<$Schema, "Membership">; +export type MembershipExistsArgs = $ExistsArgs<$Schema, "Membership">; export type MembershipCreateArgs = $CreateArgs<$Schema, "Membership">; export type MembershipCreateManyArgs = $CreateManyArgs<$Schema, "Membership">; export type MembershipCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Membership">; @@ -411,6 +431,7 @@ export type MembershipGetPayload; export type InviteFindUniqueArgs = $FindUniqueArgs<$Schema, "Invite">; export type InviteFindFirstArgs = $FindFirstArgs<$Schema, "Invite">; +export type InviteExistsArgs = $ExistsArgs<$Schema, "Invite">; export type InviteCreateArgs = $CreateArgs<$Schema, "Invite">; export type InviteCreateManyArgs = $CreateManyArgs<$Schema, "Invite">; export type InviteCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Invite">; @@ -431,6 +452,7 @@ export type InviteGetPayload; export type ApiKeyFindUniqueArgs = $FindUniqueArgs<$Schema, "ApiKey">; export type ApiKeyFindFirstArgs = $FindFirstArgs<$Schema, "ApiKey">; +export type ApiKeyExistsArgs = $ExistsArgs<$Schema, "ApiKey">; export type ApiKeyCreateArgs = $CreateArgs<$Schema, "ApiKey">; export type ApiKeyCreateManyArgs = $CreateManyArgs<$Schema, "ApiKey">; export type ApiKeyCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ApiKey">; @@ -451,6 +473,7 @@ export type ApiKeyGetPayload; export type ApiKeyEnvironmentFindUniqueArgs = $FindUniqueArgs<$Schema, "ApiKeyEnvironment">; export type ApiKeyEnvironmentFindFirstArgs = $FindFirstArgs<$Schema, "ApiKeyEnvironment">; +export type ApiKeyEnvironmentExistsArgs = $ExistsArgs<$Schema, "ApiKeyEnvironment">; export type ApiKeyEnvironmentCreateArgs = $CreateArgs<$Schema, "ApiKeyEnvironment">; export type ApiKeyEnvironmentCreateManyArgs = $CreateManyArgs<$Schema, "ApiKeyEnvironment">; export type ApiKeyEnvironmentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ApiKeyEnvironment">; @@ -471,6 +494,7 @@ export type ApiKeyEnvironmentGetPayload; export type AccountFindUniqueArgs = $FindUniqueArgs<$Schema, "Account">; export type AccountFindFirstArgs = $FindFirstArgs<$Schema, "Account">; +export type AccountExistsArgs = $ExistsArgs<$Schema, "Account">; export type AccountCreateArgs = $CreateArgs<$Schema, "Account">; export type AccountCreateManyArgs = $CreateManyArgs<$Schema, "Account">; export type AccountCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Account">; @@ -491,6 +515,7 @@ export type AccountGetPayload; 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">; @@ -511,6 +536,7 @@ export type UserGetPayload; export type ShortUrlFindUniqueArgs = $FindUniqueArgs<$Schema, "ShortUrl">; export type ShortUrlFindFirstArgs = $FindFirstArgs<$Schema, "ShortUrl">; +export type ShortUrlExistsArgs = $ExistsArgs<$Schema, "ShortUrl">; export type ShortUrlCreateArgs = $CreateArgs<$Schema, "ShortUrl">; export type ShortUrlCreateManyArgs = $CreateManyArgs<$Schema, "ShortUrl">; export type ShortUrlCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ShortUrl">; @@ -531,6 +557,7 @@ export type ShortUrlGetPayload; export type SegmentFindUniqueArgs = $FindUniqueArgs<$Schema, "Segment">; export type SegmentFindFirstArgs = $FindFirstArgs<$Schema, "Segment">; +export type SegmentExistsArgs = $ExistsArgs<$Schema, "Segment">; export type SegmentCreateArgs = $CreateArgs<$Schema, "Segment">; export type SegmentCreateManyArgs = $CreateManyArgs<$Schema, "Segment">; export type SegmentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Segment">; @@ -551,6 +578,7 @@ export type SegmentGetPayload; export type LanguageFindUniqueArgs = $FindUniqueArgs<$Schema, "Language">; export type LanguageFindFirstArgs = $FindFirstArgs<$Schema, "Language">; +export type LanguageExistsArgs = $ExistsArgs<$Schema, "Language">; export type LanguageCreateArgs = $CreateArgs<$Schema, "Language">; export type LanguageCreateManyArgs = $CreateManyArgs<$Schema, "Language">; export type LanguageCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Language">; @@ -571,6 +599,7 @@ export type LanguageGetPayload; export type SurveyLanguageFindUniqueArgs = $FindUniqueArgs<$Schema, "SurveyLanguage">; export type SurveyLanguageFindFirstArgs = $FindFirstArgs<$Schema, "SurveyLanguage">; +export type SurveyLanguageExistsArgs = $ExistsArgs<$Schema, "SurveyLanguage">; export type SurveyLanguageCreateArgs = $CreateArgs<$Schema, "SurveyLanguage">; export type SurveyLanguageCreateManyArgs = $CreateManyArgs<$Schema, "SurveyLanguage">; export type SurveyLanguageCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SurveyLanguage">; @@ -591,6 +620,7 @@ export type SurveyLanguageGetPayload; export type InsightFindUniqueArgs = $FindUniqueArgs<$Schema, "Insight">; export type InsightFindFirstArgs = $FindFirstArgs<$Schema, "Insight">; +export type InsightExistsArgs = $ExistsArgs<$Schema, "Insight">; export type InsightCreateArgs = $CreateArgs<$Schema, "Insight">; export type InsightCreateManyArgs = $CreateManyArgs<$Schema, "Insight">; export type InsightCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Insight">; @@ -611,6 +641,7 @@ export type InsightGetPayload; export type DocumentInsightFindUniqueArgs = $FindUniqueArgs<$Schema, "DocumentInsight">; export type DocumentInsightFindFirstArgs = $FindFirstArgs<$Schema, "DocumentInsight">; +export type DocumentInsightExistsArgs = $ExistsArgs<$Schema, "DocumentInsight">; export type DocumentInsightCreateArgs = $CreateArgs<$Schema, "DocumentInsight">; export type DocumentInsightCreateManyArgs = $CreateManyArgs<$Schema, "DocumentInsight">; export type DocumentInsightCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "DocumentInsight">; @@ -631,6 +662,7 @@ export type DocumentInsightGetPayload; export type DocumentFindUniqueArgs = $FindUniqueArgs<$Schema, "Document">; export type DocumentFindFirstArgs = $FindFirstArgs<$Schema, "Document">; +export type DocumentExistsArgs = $ExistsArgs<$Schema, "Document">; export type DocumentCreateArgs = $CreateArgs<$Schema, "Document">; export type DocumentCreateManyArgs = $CreateManyArgs<$Schema, "Document">; export type DocumentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Document">; @@ -651,6 +683,7 @@ export type DocumentGetPayload; export type TeamFindUniqueArgs = $FindUniqueArgs<$Schema, "Team">; export type TeamFindFirstArgs = $FindFirstArgs<$Schema, "Team">; +export type TeamExistsArgs = $ExistsArgs<$Schema, "Team">; export type TeamCreateArgs = $CreateArgs<$Schema, "Team">; export type TeamCreateManyArgs = $CreateManyArgs<$Schema, "Team">; export type TeamCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Team">; @@ -671,6 +704,7 @@ export type TeamGetPayload; export type TeamUserFindUniqueArgs = $FindUniqueArgs<$Schema, "TeamUser">; export type TeamUserFindFirstArgs = $FindFirstArgs<$Schema, "TeamUser">; +export type TeamUserExistsArgs = $ExistsArgs<$Schema, "TeamUser">; export type TeamUserCreateArgs = $CreateArgs<$Schema, "TeamUser">; export type TeamUserCreateManyArgs = $CreateManyArgs<$Schema, "TeamUser">; export type TeamUserCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TeamUser">; @@ -691,6 +725,7 @@ export type TeamUserGetPayload; export type ProjectTeamFindUniqueArgs = $FindUniqueArgs<$Schema, "ProjectTeam">; export type ProjectTeamFindFirstArgs = $FindFirstArgs<$Schema, "ProjectTeam">; +export type ProjectTeamExistsArgs = $ExistsArgs<$Schema, "ProjectTeam">; export type ProjectTeamCreateArgs = $CreateArgs<$Schema, "ProjectTeam">; export type ProjectTeamCreateManyArgs = $CreateManyArgs<$Schema, "ProjectTeam">; export type ProjectTeamCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ProjectTeam">; diff --git a/tests/e2e/github-repos/trigger.dev/input.ts b/tests/e2e/github-repos/trigger.dev/input.ts index ac5e40dec..394583e39 100644 --- a/tests/e2e/github-repos/trigger.dev/input.ts +++ b/tests/e2e/github-repos/trigger.dev/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; export type InvitationCodeFindUniqueArgs = $FindUniqueArgs<$Schema, "InvitationCode">; export type InvitationCodeFindFirstArgs = $FindFirstArgs<$Schema, "InvitationCode">; +export type InvitationCodeExistsArgs = $ExistsArgs<$Schema, "InvitationCode">; export type InvitationCodeCreateArgs = $CreateArgs<$Schema, "InvitationCode">; export type InvitationCodeCreateManyArgs = $CreateManyArgs<$Schema, "InvitationCode">; export type InvitationCodeCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "InvitationCode">; @@ -51,6 +53,7 @@ export type InvitationCodeGetPayload; export type AuthorizationCodeFindUniqueArgs = $FindUniqueArgs<$Schema, "AuthorizationCode">; export type AuthorizationCodeFindFirstArgs = $FindFirstArgs<$Schema, "AuthorizationCode">; +export type AuthorizationCodeExistsArgs = $ExistsArgs<$Schema, "AuthorizationCode">; export type AuthorizationCodeCreateArgs = $CreateArgs<$Schema, "AuthorizationCode">; export type AuthorizationCodeCreateManyArgs = $CreateManyArgs<$Schema, "AuthorizationCode">; export type AuthorizationCodeCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "AuthorizationCode">; @@ -71,6 +74,7 @@ export type AuthorizationCodeGetPayload; export type PersonalAccessTokenFindUniqueArgs = $FindUniqueArgs<$Schema, "PersonalAccessToken">; export type PersonalAccessTokenFindFirstArgs = $FindFirstArgs<$Schema, "PersonalAccessToken">; +export type PersonalAccessTokenExistsArgs = $ExistsArgs<$Schema, "PersonalAccessToken">; export type PersonalAccessTokenCreateArgs = $CreateArgs<$Schema, "PersonalAccessToken">; export type PersonalAccessTokenCreateManyArgs = $CreateManyArgs<$Schema, "PersonalAccessToken">; export type PersonalAccessTokenCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "PersonalAccessToken">; @@ -91,6 +95,7 @@ export type PersonalAccessTokenGetPayload; export type OrganizationFindUniqueArgs = $FindUniqueArgs<$Schema, "Organization">; export type OrganizationFindFirstArgs = $FindFirstArgs<$Schema, "Organization">; +export type OrganizationExistsArgs = $ExistsArgs<$Schema, "Organization">; export type OrganizationCreateArgs = $CreateArgs<$Schema, "Organization">; export type OrganizationCreateManyArgs = $CreateManyArgs<$Schema, "Organization">; export type OrganizationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Organization">; @@ -111,6 +116,7 @@ export type OrganizationGetPayload; export type OrgMemberFindUniqueArgs = $FindUniqueArgs<$Schema, "OrgMember">; export type OrgMemberFindFirstArgs = $FindFirstArgs<$Schema, "OrgMember">; +export type OrgMemberExistsArgs = $ExistsArgs<$Schema, "OrgMember">; export type OrgMemberCreateArgs = $CreateArgs<$Schema, "OrgMember">; export type OrgMemberCreateManyArgs = $CreateManyArgs<$Schema, "OrgMember">; export type OrgMemberCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "OrgMember">; @@ -131,6 +137,7 @@ export type OrgMemberGetPayload; export type OrgMemberInviteFindUniqueArgs = $FindUniqueArgs<$Schema, "OrgMemberInvite">; export type OrgMemberInviteFindFirstArgs = $FindFirstArgs<$Schema, "OrgMemberInvite">; +export type OrgMemberInviteExistsArgs = $ExistsArgs<$Schema, "OrgMemberInvite">; export type OrgMemberInviteCreateArgs = $CreateArgs<$Schema, "OrgMemberInvite">; export type OrgMemberInviteCreateManyArgs = $CreateManyArgs<$Schema, "OrgMemberInvite">; export type OrgMemberInviteCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "OrgMemberInvite">; @@ -151,6 +158,7 @@ export type OrgMemberInviteGetPayload; export type RuntimeEnvironmentFindUniqueArgs = $FindUniqueArgs<$Schema, "RuntimeEnvironment">; export type RuntimeEnvironmentFindFirstArgs = $FindFirstArgs<$Schema, "RuntimeEnvironment">; +export type RuntimeEnvironmentExistsArgs = $ExistsArgs<$Schema, "RuntimeEnvironment">; export type RuntimeEnvironmentCreateArgs = $CreateArgs<$Schema, "RuntimeEnvironment">; export type RuntimeEnvironmentCreateManyArgs = $CreateManyArgs<$Schema, "RuntimeEnvironment">; export type RuntimeEnvironmentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RuntimeEnvironment">; @@ -171,6 +179,7 @@ export type RuntimeEnvironmentGetPayload; export type ProjectFindUniqueArgs = $FindUniqueArgs<$Schema, "Project">; export type ProjectFindFirstArgs = $FindFirstArgs<$Schema, "Project">; +export type ProjectExistsArgs = $ExistsArgs<$Schema, "Project">; export type ProjectCreateArgs = $CreateArgs<$Schema, "Project">; export type ProjectCreateManyArgs = $CreateManyArgs<$Schema, "Project">; export type ProjectCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Project">; @@ -191,6 +200,7 @@ export type ProjectGetPayload; export type SecretReferenceFindUniqueArgs = $FindUniqueArgs<$Schema, "SecretReference">; export type SecretReferenceFindFirstArgs = $FindFirstArgs<$Schema, "SecretReference">; +export type SecretReferenceExistsArgs = $ExistsArgs<$Schema, "SecretReference">; export type SecretReferenceCreateArgs = $CreateArgs<$Schema, "SecretReference">; export type SecretReferenceCreateManyArgs = $CreateManyArgs<$Schema, "SecretReference">; export type SecretReferenceCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SecretReference">; @@ -211,6 +221,7 @@ export type SecretReferenceGetPayload; export type SecretStoreFindUniqueArgs = $FindUniqueArgs<$Schema, "SecretStore">; export type SecretStoreFindFirstArgs = $FindFirstArgs<$Schema, "SecretStore">; +export type SecretStoreExistsArgs = $ExistsArgs<$Schema, "SecretStore">; export type SecretStoreCreateArgs = $CreateArgs<$Schema, "SecretStore">; export type SecretStoreCreateManyArgs = $CreateManyArgs<$Schema, "SecretStore">; export type SecretStoreCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SecretStore">; @@ -231,6 +242,7 @@ export type SecretStoreGetPayload; export type DataMigrationFindUniqueArgs = $FindUniqueArgs<$Schema, "DataMigration">; export type DataMigrationFindFirstArgs = $FindFirstArgs<$Schema, "DataMigration">; +export type DataMigrationExistsArgs = $ExistsArgs<$Schema, "DataMigration">; export type DataMigrationCreateArgs = $CreateArgs<$Schema, "DataMigration">; export type DataMigrationCreateManyArgs = $CreateManyArgs<$Schema, "DataMigration">; export type DataMigrationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "DataMigration">; @@ -251,6 +263,7 @@ export type DataMigrationGetPayload; export type BackgroundWorkerFindUniqueArgs = $FindUniqueArgs<$Schema, "BackgroundWorker">; export type BackgroundWorkerFindFirstArgs = $FindFirstArgs<$Schema, "BackgroundWorker">; +export type BackgroundWorkerExistsArgs = $ExistsArgs<$Schema, "BackgroundWorker">; export type BackgroundWorkerCreateArgs = $CreateArgs<$Schema, "BackgroundWorker">; export type BackgroundWorkerCreateManyArgs = $CreateManyArgs<$Schema, "BackgroundWorker">; export type BackgroundWorkerCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BackgroundWorker">; @@ -271,6 +284,7 @@ export type BackgroundWorkerGetPayload; export type BackgroundWorkerFileFindUniqueArgs = $FindUniqueArgs<$Schema, "BackgroundWorkerFile">; export type BackgroundWorkerFileFindFirstArgs = $FindFirstArgs<$Schema, "BackgroundWorkerFile">; +export type BackgroundWorkerFileExistsArgs = $ExistsArgs<$Schema, "BackgroundWorkerFile">; export type BackgroundWorkerFileCreateArgs = $CreateArgs<$Schema, "BackgroundWorkerFile">; export type BackgroundWorkerFileCreateManyArgs = $CreateManyArgs<$Schema, "BackgroundWorkerFile">; export type BackgroundWorkerFileCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BackgroundWorkerFile">; @@ -291,6 +305,7 @@ export type BackgroundWorkerFileGetPayload; export type BackgroundWorkerTaskFindUniqueArgs = $FindUniqueArgs<$Schema, "BackgroundWorkerTask">; export type BackgroundWorkerTaskFindFirstArgs = $FindFirstArgs<$Schema, "BackgroundWorkerTask">; +export type BackgroundWorkerTaskExistsArgs = $ExistsArgs<$Schema, "BackgroundWorkerTask">; export type BackgroundWorkerTaskCreateArgs = $CreateArgs<$Schema, "BackgroundWorkerTask">; export type BackgroundWorkerTaskCreateManyArgs = $CreateManyArgs<$Schema, "BackgroundWorkerTask">; export type BackgroundWorkerTaskCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BackgroundWorkerTask">; @@ -311,6 +326,7 @@ export type BackgroundWorkerTaskGetPayload; export type TaskRunFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRun">; export type TaskRunFindFirstArgs = $FindFirstArgs<$Schema, "TaskRun">; +export type TaskRunExistsArgs = $ExistsArgs<$Schema, "TaskRun">; export type TaskRunCreateArgs = $CreateArgs<$Schema, "TaskRun">; export type TaskRunCreateManyArgs = $CreateManyArgs<$Schema, "TaskRun">; export type TaskRunCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRun">; @@ -331,6 +347,7 @@ export type TaskRunGetPayload; export type TaskRunExecutionSnapshotFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRunExecutionSnapshot">; export type TaskRunExecutionSnapshotFindFirstArgs = $FindFirstArgs<$Schema, "TaskRunExecutionSnapshot">; +export type TaskRunExecutionSnapshotExistsArgs = $ExistsArgs<$Schema, "TaskRunExecutionSnapshot">; export type TaskRunExecutionSnapshotCreateArgs = $CreateArgs<$Schema, "TaskRunExecutionSnapshot">; export type TaskRunExecutionSnapshotCreateManyArgs = $CreateManyArgs<$Schema, "TaskRunExecutionSnapshot">; export type TaskRunExecutionSnapshotCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRunExecutionSnapshot">; @@ -351,6 +368,7 @@ export type TaskRunExecutionSnapshotGetPayload; export type TaskRunCheckpointFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRunCheckpoint">; export type TaskRunCheckpointFindFirstArgs = $FindFirstArgs<$Schema, "TaskRunCheckpoint">; +export type TaskRunCheckpointExistsArgs = $ExistsArgs<$Schema, "TaskRunCheckpoint">; export type TaskRunCheckpointCreateArgs = $CreateArgs<$Schema, "TaskRunCheckpoint">; export type TaskRunCheckpointCreateManyArgs = $CreateManyArgs<$Schema, "TaskRunCheckpoint">; export type TaskRunCheckpointCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRunCheckpoint">; @@ -371,6 +389,7 @@ export type TaskRunCheckpointGetPayload; export type WaitpointFindUniqueArgs = $FindUniqueArgs<$Schema, "Waitpoint">; export type WaitpointFindFirstArgs = $FindFirstArgs<$Schema, "Waitpoint">; +export type WaitpointExistsArgs = $ExistsArgs<$Schema, "Waitpoint">; export type WaitpointCreateArgs = $CreateArgs<$Schema, "Waitpoint">; export type WaitpointCreateManyArgs = $CreateManyArgs<$Schema, "Waitpoint">; export type WaitpointCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Waitpoint">; @@ -391,6 +410,7 @@ export type WaitpointGetPayload; export type TaskRunWaitpointFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRunWaitpoint">; export type TaskRunWaitpointFindFirstArgs = $FindFirstArgs<$Schema, "TaskRunWaitpoint">; +export type TaskRunWaitpointExistsArgs = $ExistsArgs<$Schema, "TaskRunWaitpoint">; export type TaskRunWaitpointCreateArgs = $CreateArgs<$Schema, "TaskRunWaitpoint">; export type TaskRunWaitpointCreateManyArgs = $CreateManyArgs<$Schema, "TaskRunWaitpoint">; export type TaskRunWaitpointCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRunWaitpoint">; @@ -411,6 +431,7 @@ export type TaskRunWaitpointGetPayload; export type WaitpointTagFindUniqueArgs = $FindUniqueArgs<$Schema, "WaitpointTag">; export type WaitpointTagFindFirstArgs = $FindFirstArgs<$Schema, "WaitpointTag">; +export type WaitpointTagExistsArgs = $ExistsArgs<$Schema, "WaitpointTag">; export type WaitpointTagCreateArgs = $CreateArgs<$Schema, "WaitpointTag">; export type WaitpointTagCreateManyArgs = $CreateManyArgs<$Schema, "WaitpointTag">; export type WaitpointTagCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WaitpointTag">; @@ -431,6 +452,7 @@ export type WaitpointTagGetPayload; export type FeatureFlagFindUniqueArgs = $FindUniqueArgs<$Schema, "FeatureFlag">; export type FeatureFlagFindFirstArgs = $FindFirstArgs<$Schema, "FeatureFlag">; +export type FeatureFlagExistsArgs = $ExistsArgs<$Schema, "FeatureFlag">; export type FeatureFlagCreateArgs = $CreateArgs<$Schema, "FeatureFlag">; export type FeatureFlagCreateManyArgs = $CreateManyArgs<$Schema, "FeatureFlag">; export type FeatureFlagCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "FeatureFlag">; @@ -451,6 +473,7 @@ export type FeatureFlagGetPayload; export type WorkerInstanceFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkerInstance">; export type WorkerInstanceFindFirstArgs = $FindFirstArgs<$Schema, "WorkerInstance">; +export type WorkerInstanceExistsArgs = $ExistsArgs<$Schema, "WorkerInstance">; export type WorkerInstanceCreateArgs = $CreateArgs<$Schema, "WorkerInstance">; export type WorkerInstanceCreateManyArgs = $CreateManyArgs<$Schema, "WorkerInstance">; export type WorkerInstanceCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkerInstance">; @@ -471,6 +494,7 @@ export type WorkerInstanceGetPayload; export type WorkerInstanceGroupFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkerInstanceGroup">; export type WorkerInstanceGroupFindFirstArgs = $FindFirstArgs<$Schema, "WorkerInstanceGroup">; +export type WorkerInstanceGroupExistsArgs = $ExistsArgs<$Schema, "WorkerInstanceGroup">; export type WorkerInstanceGroupCreateArgs = $CreateArgs<$Schema, "WorkerInstanceGroup">; export type WorkerInstanceGroupCreateManyArgs = $CreateManyArgs<$Schema, "WorkerInstanceGroup">; export type WorkerInstanceGroupCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkerInstanceGroup">; @@ -491,6 +515,7 @@ export type WorkerInstanceGroupGetPayload; export type WorkerGroupTokenFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkerGroupToken">; export type WorkerGroupTokenFindFirstArgs = $FindFirstArgs<$Schema, "WorkerGroupToken">; +export type WorkerGroupTokenExistsArgs = $ExistsArgs<$Schema, "WorkerGroupToken">; export type WorkerGroupTokenCreateArgs = $CreateArgs<$Schema, "WorkerGroupToken">; export type WorkerGroupTokenCreateManyArgs = $CreateManyArgs<$Schema, "WorkerGroupToken">; export type WorkerGroupTokenCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkerGroupToken">; @@ -511,6 +536,7 @@ export type WorkerGroupTokenGetPayload; export type TaskRunTagFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRunTag">; export type TaskRunTagFindFirstArgs = $FindFirstArgs<$Schema, "TaskRunTag">; +export type TaskRunTagExistsArgs = $ExistsArgs<$Schema, "TaskRunTag">; export type TaskRunTagCreateArgs = $CreateArgs<$Schema, "TaskRunTag">; export type TaskRunTagCreateManyArgs = $CreateManyArgs<$Schema, "TaskRunTag">; export type TaskRunTagCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRunTag">; @@ -531,6 +557,7 @@ export type TaskRunTagGetPayload; export type TaskRunDependencyFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRunDependency">; export type TaskRunDependencyFindFirstArgs = $FindFirstArgs<$Schema, "TaskRunDependency">; +export type TaskRunDependencyExistsArgs = $ExistsArgs<$Schema, "TaskRunDependency">; export type TaskRunDependencyCreateArgs = $CreateArgs<$Schema, "TaskRunDependency">; export type TaskRunDependencyCreateManyArgs = $CreateManyArgs<$Schema, "TaskRunDependency">; export type TaskRunDependencyCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRunDependency">; @@ -551,6 +578,7 @@ export type TaskRunDependencyGetPayload; export type TaskRunCounterFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRunCounter">; export type TaskRunCounterFindFirstArgs = $FindFirstArgs<$Schema, "TaskRunCounter">; +export type TaskRunCounterExistsArgs = $ExistsArgs<$Schema, "TaskRunCounter">; export type TaskRunCounterCreateArgs = $CreateArgs<$Schema, "TaskRunCounter">; export type TaskRunCounterCreateManyArgs = $CreateManyArgs<$Schema, "TaskRunCounter">; export type TaskRunCounterCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRunCounter">; @@ -571,6 +599,7 @@ export type TaskRunCounterGetPayload; export type TaskRunNumberCounterFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRunNumberCounter">; export type TaskRunNumberCounterFindFirstArgs = $FindFirstArgs<$Schema, "TaskRunNumberCounter">; +export type TaskRunNumberCounterExistsArgs = $ExistsArgs<$Schema, "TaskRunNumberCounter">; export type TaskRunNumberCounterCreateArgs = $CreateArgs<$Schema, "TaskRunNumberCounter">; export type TaskRunNumberCounterCreateManyArgs = $CreateManyArgs<$Schema, "TaskRunNumberCounter">; export type TaskRunNumberCounterCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRunNumberCounter">; @@ -591,6 +620,7 @@ export type TaskRunNumberCounterGetPayload; export type TaskRunAttemptFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskRunAttempt">; export type TaskRunAttemptFindFirstArgs = $FindFirstArgs<$Schema, "TaskRunAttempt">; +export type TaskRunAttemptExistsArgs = $ExistsArgs<$Schema, "TaskRunAttempt">; export type TaskRunAttemptCreateArgs = $CreateArgs<$Schema, "TaskRunAttempt">; export type TaskRunAttemptCreateManyArgs = $CreateManyArgs<$Schema, "TaskRunAttempt">; export type TaskRunAttemptCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskRunAttempt">; @@ -611,6 +641,7 @@ export type TaskRunAttemptGetPayload; export type TaskEventFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskEvent">; export type TaskEventFindFirstArgs = $FindFirstArgs<$Schema, "TaskEvent">; +export type TaskEventExistsArgs = $ExistsArgs<$Schema, "TaskEvent">; export type TaskEventCreateArgs = $CreateArgs<$Schema, "TaskEvent">; export type TaskEventCreateManyArgs = $CreateManyArgs<$Schema, "TaskEvent">; export type TaskEventCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskEvent">; @@ -631,6 +662,7 @@ export type TaskEventGetPayload; export type TaskQueueFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskQueue">; export type TaskQueueFindFirstArgs = $FindFirstArgs<$Schema, "TaskQueue">; +export type TaskQueueExistsArgs = $ExistsArgs<$Schema, "TaskQueue">; export type TaskQueueCreateArgs = $CreateArgs<$Schema, "TaskQueue">; export type TaskQueueCreateManyArgs = $CreateManyArgs<$Schema, "TaskQueue">; export type TaskQueueCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskQueue">; @@ -651,6 +683,7 @@ export type TaskQueueGetPayload; export type BatchTaskRunFindUniqueArgs = $FindUniqueArgs<$Schema, "BatchTaskRun">; export type BatchTaskRunFindFirstArgs = $FindFirstArgs<$Schema, "BatchTaskRun">; +export type BatchTaskRunExistsArgs = $ExistsArgs<$Schema, "BatchTaskRun">; export type BatchTaskRunCreateArgs = $CreateArgs<$Schema, "BatchTaskRun">; export type BatchTaskRunCreateManyArgs = $CreateManyArgs<$Schema, "BatchTaskRun">; export type BatchTaskRunCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BatchTaskRun">; @@ -671,6 +704,7 @@ export type BatchTaskRunGetPayload; export type BatchTaskRunItemFindUniqueArgs = $FindUniqueArgs<$Schema, "BatchTaskRunItem">; export type BatchTaskRunItemFindFirstArgs = $FindFirstArgs<$Schema, "BatchTaskRunItem">; +export type BatchTaskRunItemExistsArgs = $ExistsArgs<$Schema, "BatchTaskRunItem">; export type BatchTaskRunItemCreateArgs = $CreateArgs<$Schema, "BatchTaskRunItem">; export type BatchTaskRunItemCreateManyArgs = $CreateManyArgs<$Schema, "BatchTaskRunItem">; export type BatchTaskRunItemCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BatchTaskRunItem">; @@ -691,6 +725,7 @@ export type BatchTaskRunItemGetPayload; export type EnvironmentVariableFindUniqueArgs = $FindUniqueArgs<$Schema, "EnvironmentVariable">; export type EnvironmentVariableFindFirstArgs = $FindFirstArgs<$Schema, "EnvironmentVariable">; +export type EnvironmentVariableExistsArgs = $ExistsArgs<$Schema, "EnvironmentVariable">; export type EnvironmentVariableCreateArgs = $CreateArgs<$Schema, "EnvironmentVariable">; export type EnvironmentVariableCreateManyArgs = $CreateManyArgs<$Schema, "EnvironmentVariable">; export type EnvironmentVariableCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "EnvironmentVariable">; @@ -711,6 +746,7 @@ export type EnvironmentVariableGetPayload; export type EnvironmentVariableValueFindUniqueArgs = $FindUniqueArgs<$Schema, "EnvironmentVariableValue">; export type EnvironmentVariableValueFindFirstArgs = $FindFirstArgs<$Schema, "EnvironmentVariableValue">; +export type EnvironmentVariableValueExistsArgs = $ExistsArgs<$Schema, "EnvironmentVariableValue">; export type EnvironmentVariableValueCreateArgs = $CreateArgs<$Schema, "EnvironmentVariableValue">; export type EnvironmentVariableValueCreateManyArgs = $CreateManyArgs<$Schema, "EnvironmentVariableValue">; export type EnvironmentVariableValueCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "EnvironmentVariableValue">; @@ -731,6 +767,7 @@ export type EnvironmentVariableValueGetPayload; export type CheckpointFindUniqueArgs = $FindUniqueArgs<$Schema, "Checkpoint">; export type CheckpointFindFirstArgs = $FindFirstArgs<$Schema, "Checkpoint">; +export type CheckpointExistsArgs = $ExistsArgs<$Schema, "Checkpoint">; export type CheckpointCreateArgs = $CreateArgs<$Schema, "Checkpoint">; export type CheckpointCreateManyArgs = $CreateManyArgs<$Schema, "Checkpoint">; export type CheckpointCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Checkpoint">; @@ -751,6 +788,7 @@ export type CheckpointGetPayload; export type CheckpointRestoreEventFindUniqueArgs = $FindUniqueArgs<$Schema, "CheckpointRestoreEvent">; export type CheckpointRestoreEventFindFirstArgs = $FindFirstArgs<$Schema, "CheckpointRestoreEvent">; +export type CheckpointRestoreEventExistsArgs = $ExistsArgs<$Schema, "CheckpointRestoreEvent">; export type CheckpointRestoreEventCreateArgs = $CreateArgs<$Schema, "CheckpointRestoreEvent">; export type CheckpointRestoreEventCreateManyArgs = $CreateManyArgs<$Schema, "CheckpointRestoreEvent">; export type CheckpointRestoreEventCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "CheckpointRestoreEvent">; @@ -771,6 +809,7 @@ export type CheckpointRestoreEventGetPayload; export type WorkerDeploymentFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkerDeployment">; export type WorkerDeploymentFindFirstArgs = $FindFirstArgs<$Schema, "WorkerDeployment">; +export type WorkerDeploymentExistsArgs = $ExistsArgs<$Schema, "WorkerDeployment">; export type WorkerDeploymentCreateArgs = $CreateArgs<$Schema, "WorkerDeployment">; export type WorkerDeploymentCreateManyArgs = $CreateManyArgs<$Schema, "WorkerDeployment">; export type WorkerDeploymentCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkerDeployment">; @@ -791,6 +830,7 @@ export type WorkerDeploymentGetPayload; export type WorkerDeploymentPromotionFindUniqueArgs = $FindUniqueArgs<$Schema, "WorkerDeploymentPromotion">; export type WorkerDeploymentPromotionFindFirstArgs = $FindFirstArgs<$Schema, "WorkerDeploymentPromotion">; +export type WorkerDeploymentPromotionExistsArgs = $ExistsArgs<$Schema, "WorkerDeploymentPromotion">; export type WorkerDeploymentPromotionCreateArgs = $CreateArgs<$Schema, "WorkerDeploymentPromotion">; export type WorkerDeploymentPromotionCreateManyArgs = $CreateManyArgs<$Schema, "WorkerDeploymentPromotion">; export type WorkerDeploymentPromotionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "WorkerDeploymentPromotion">; @@ -811,6 +851,7 @@ export type WorkerDeploymentPromotionGetPayload; export type TaskScheduleFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskSchedule">; export type TaskScheduleFindFirstArgs = $FindFirstArgs<$Schema, "TaskSchedule">; +export type TaskScheduleExistsArgs = $ExistsArgs<$Schema, "TaskSchedule">; export type TaskScheduleCreateArgs = $CreateArgs<$Schema, "TaskSchedule">; export type TaskScheduleCreateManyArgs = $CreateManyArgs<$Schema, "TaskSchedule">; export type TaskScheduleCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskSchedule">; @@ -831,6 +872,7 @@ export type TaskScheduleGetPayload; export type TaskScheduleInstanceFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskScheduleInstance">; export type TaskScheduleInstanceFindFirstArgs = $FindFirstArgs<$Schema, "TaskScheduleInstance">; +export type TaskScheduleInstanceExistsArgs = $ExistsArgs<$Schema, "TaskScheduleInstance">; export type TaskScheduleInstanceCreateArgs = $CreateArgs<$Schema, "TaskScheduleInstance">; export type TaskScheduleInstanceCreateManyArgs = $CreateManyArgs<$Schema, "TaskScheduleInstance">; export type TaskScheduleInstanceCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskScheduleInstance">; @@ -851,6 +893,7 @@ export type TaskScheduleInstanceGetPayload; export type RuntimeEnvironmentSessionFindUniqueArgs = $FindUniqueArgs<$Schema, "RuntimeEnvironmentSession">; export type RuntimeEnvironmentSessionFindFirstArgs = $FindFirstArgs<$Schema, "RuntimeEnvironmentSession">; +export type RuntimeEnvironmentSessionExistsArgs = $ExistsArgs<$Schema, "RuntimeEnvironmentSession">; export type RuntimeEnvironmentSessionCreateArgs = $CreateArgs<$Schema, "RuntimeEnvironmentSession">; export type RuntimeEnvironmentSessionCreateManyArgs = $CreateManyArgs<$Schema, "RuntimeEnvironmentSession">; export type RuntimeEnvironmentSessionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RuntimeEnvironmentSession">; @@ -871,6 +914,7 @@ export type RuntimeEnvironmentSessionGetPayload; export type ProjectAlertChannelFindUniqueArgs = $FindUniqueArgs<$Schema, "ProjectAlertChannel">; export type ProjectAlertChannelFindFirstArgs = $FindFirstArgs<$Schema, "ProjectAlertChannel">; +export type ProjectAlertChannelExistsArgs = $ExistsArgs<$Schema, "ProjectAlertChannel">; export type ProjectAlertChannelCreateArgs = $CreateArgs<$Schema, "ProjectAlertChannel">; export type ProjectAlertChannelCreateManyArgs = $CreateManyArgs<$Schema, "ProjectAlertChannel">; export type ProjectAlertChannelCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ProjectAlertChannel">; @@ -891,6 +935,7 @@ export type ProjectAlertChannelGetPayload; export type ProjectAlertFindUniqueArgs = $FindUniqueArgs<$Schema, "ProjectAlert">; export type ProjectAlertFindFirstArgs = $FindFirstArgs<$Schema, "ProjectAlert">; +export type ProjectAlertExistsArgs = $ExistsArgs<$Schema, "ProjectAlert">; export type ProjectAlertCreateArgs = $CreateArgs<$Schema, "ProjectAlert">; export type ProjectAlertCreateManyArgs = $CreateManyArgs<$Schema, "ProjectAlert">; export type ProjectAlertCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ProjectAlert">; @@ -911,6 +956,7 @@ export type ProjectAlertGetPayload; export type ProjectAlertStorageFindUniqueArgs = $FindUniqueArgs<$Schema, "ProjectAlertStorage">; export type ProjectAlertStorageFindFirstArgs = $FindFirstArgs<$Schema, "ProjectAlertStorage">; +export type ProjectAlertStorageExistsArgs = $ExistsArgs<$Schema, "ProjectAlertStorage">; export type ProjectAlertStorageCreateArgs = $CreateArgs<$Schema, "ProjectAlertStorage">; export type ProjectAlertStorageCreateManyArgs = $CreateManyArgs<$Schema, "ProjectAlertStorage">; export type ProjectAlertStorageCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "ProjectAlertStorage">; @@ -931,6 +977,7 @@ export type ProjectAlertStorageGetPayload; export type OrganizationIntegrationFindUniqueArgs = $FindUniqueArgs<$Schema, "OrganizationIntegration">; export type OrganizationIntegrationFindFirstArgs = $FindFirstArgs<$Schema, "OrganizationIntegration">; +export type OrganizationIntegrationExistsArgs = $ExistsArgs<$Schema, "OrganizationIntegration">; export type OrganizationIntegrationCreateArgs = $CreateArgs<$Schema, "OrganizationIntegration">; export type OrganizationIntegrationCreateManyArgs = $CreateManyArgs<$Schema, "OrganizationIntegration">; export type OrganizationIntegrationCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "OrganizationIntegration">; @@ -951,6 +998,7 @@ export type OrganizationIntegrationGetPayload; export type BulkActionGroupFindUniqueArgs = $FindUniqueArgs<$Schema, "BulkActionGroup">; export type BulkActionGroupFindFirstArgs = $FindFirstArgs<$Schema, "BulkActionGroup">; +export type BulkActionGroupExistsArgs = $ExistsArgs<$Schema, "BulkActionGroup">; export type BulkActionGroupCreateArgs = $CreateArgs<$Schema, "BulkActionGroup">; export type BulkActionGroupCreateManyArgs = $CreateManyArgs<$Schema, "BulkActionGroup">; export type BulkActionGroupCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BulkActionGroup">; @@ -971,6 +1019,7 @@ export type BulkActionGroupGetPayload; export type BulkActionItemFindUniqueArgs = $FindUniqueArgs<$Schema, "BulkActionItem">; export type BulkActionItemFindFirstArgs = $FindFirstArgs<$Schema, "BulkActionItem">; +export type BulkActionItemExistsArgs = $ExistsArgs<$Schema, "BulkActionItem">; export type BulkActionItemCreateArgs = $CreateArgs<$Schema, "BulkActionItem">; export type BulkActionItemCreateManyArgs = $CreateManyArgs<$Schema, "BulkActionItem">; export type BulkActionItemCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "BulkActionItem">; @@ -991,6 +1040,7 @@ export type BulkActionItemGetPayload; export type RealtimeStreamChunkFindUniqueArgs = $FindUniqueArgs<$Schema, "RealtimeStreamChunk">; export type RealtimeStreamChunkFindFirstArgs = $FindFirstArgs<$Schema, "RealtimeStreamChunk">; +export type RealtimeStreamChunkExistsArgs = $ExistsArgs<$Schema, "RealtimeStreamChunk">; export type RealtimeStreamChunkCreateArgs = $CreateArgs<$Schema, "RealtimeStreamChunk">; export type RealtimeStreamChunkCreateManyArgs = $CreateManyArgs<$Schema, "RealtimeStreamChunk">; export type RealtimeStreamChunkCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RealtimeStreamChunk">; @@ -1011,6 +1061,7 @@ export type RealtimeStreamChunkGetPayload; export type TaskEventPartitionedFindUniqueArgs = $FindUniqueArgs<$Schema, "TaskEventPartitioned">; export type TaskEventPartitionedFindFirstArgs = $FindFirstArgs<$Schema, "TaskEventPartitioned">; +export type TaskEventPartitionedExistsArgs = $ExistsArgs<$Schema, "TaskEventPartitioned">; export type TaskEventPartitionedCreateArgs = $CreateArgs<$Schema, "TaskEventPartitioned">; export type TaskEventPartitionedCreateManyArgs = $CreateManyArgs<$Schema, "TaskEventPartitioned">; export type TaskEventPartitionedCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "TaskEventPartitioned">; diff --git a/tests/e2e/orm/client-api/exists.test.ts b/tests/e2e/orm/client-api/exists.test.ts new file mode 100644 index 000000000..42e4e8c19 --- /dev/null +++ b/tests/e2e/orm/client-api/exists.test.ts @@ -0,0 +1,180 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import type { ClientContract } from '@zenstackhq/orm'; +import { schema } from '../schemas/basic'; +import { createTestClient } from '@zenstackhq/testtools'; + +describe('Client exists tests', () => { + let client: ClientContract; + + beforeEach(async () => { + client = await createTestClient(schema); + }); + + afterEach(async () => { + await client?.$disconnect(); + }); + + it('works with no args', async () => { + await expect(client.user.exists()).resolves.toBe(false); + + await client.user.create({ + data: { + email: 'test@email.com', + }, + }); + + await expect(client.user.exists()).resolves.toBe(true); + }); + + it('works with empty args', async () => { + await expect(client.user.exists({})).resolves.toBe(false); + + await client.user.create({ + data: { + email: 'test@email.com', + }, + }); + + await expect(client.user.exists({})).resolves.toBe(true); + }); + + it('works with empty where', async () => { + await expect(client.user.exists({ where: {} })).resolves.toBe(false); + + await client.user.create({ + data: { + email: 'test@email.com', + }, + }); + + await expect(client.user.exists({ where: {} })).resolves.toBe(true); + }); + + it('works with toplevel', async () => { + await client.user.create({ + data: { + email: 'test@email.com', + }, + }); + + await expect(client.user.exists({ + where: { + email: 'test@email.com', + }, + })).resolves.toBe(true); + + await expect(client.user.exists({ + where: { + email: 'wrong@email.com', + }, + })).resolves.toBe(false); + }); + + it('works with nested', async () => { + await client.user.create({ + data: { + email: 'test@email.com', + posts: { + create: { + title: 'Test title', + }, + }, + }, + }); + + await expect(client.user.exists({ + where: { + posts: { + some: { + title: 'Test title', + }, + }, + }, + })).resolves.toBe(true); + + await expect(client.user.exists({ + where: { + posts: { + some: { + title: 'Wrong test title', + }, + }, + }, + })).resolves.toBe(false); + + await expect(client.post.exists({ + where: { + title: 'Test title', + } + })).resolves.toBe(true); + + await expect(client.post.exists({ + where: { + title: 'Wrong test title', + } + })).resolves.toBe(false); + }); + + it('works with deeply nested', async () => { + await client.user.create({ + data: { + email: 'test@email.com', + posts: { + create: { + title: 'Test title', + comments: { + create: { + content: 'Test content', + }, + }, + }, + }, + }, + }); + + await expect(client.user.exists({ + where: { + posts: { + some: { + title: 'Test title', + comments: { + some: { + content: 'Test content', + }, + }, + }, + }, + }, + })).resolves.toBe(true); + + await expect(client.user.exists({ + where: { + posts: { + some: { + title: 'Test title', + comments: { + some: { + content: 'Wrong test content', + }, + }, + }, + }, + }, + })).resolves.toBe(false); + + await expect(client.user.exists({ + where: { + posts: { + some: { + title: 'Wrong test title', + comments: { + some: { + content: 'Test content', + }, + }, + }, + }, + }, + })).resolves.toBe(false); + }); +}); \ No newline at end of file diff --git a/tests/e2e/orm/client-api/pg-custom-schema.test.ts b/tests/e2e/orm/client-api/pg-custom-schema.test.ts index dfe6fe895..6fae284ba 100644 --- a/tests/e2e/orm/client-api/pg-custom-schema.test.ts +++ b/tests/e2e/orm/client-api/pg-custom-schema.test.ts @@ -159,7 +159,7 @@ model Foo { enum BarRole { ADMIN USER - @@schema('public') + @@schema('mySchema') } model Bar { diff --git a/tests/e2e/orm/client-api/procedures.test.ts b/tests/e2e/orm/client-api/procedures.test.ts new file mode 100644 index 000000000..6bd7b5bfd --- /dev/null +++ b/tests/e2e/orm/client-api/procedures.test.ts @@ -0,0 +1,217 @@ +import type { ClientContract } from '@zenstackhq/orm'; +import { createTestClient } from '@zenstackhq/testtools'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { schema } from '../schemas/procedures/schema'; +import type { User } from '../schemas/procedures/models'; + +describe('Procedures tests', () => { + let client: ClientContract; + + beforeEach(async () => { + client = await createTestClient(schema, { + procedures: { + // Query procedure that returns a single User + getUser: async ({ client, args: { id } }) => { + return await client.user.findUniqueOrThrow({ + where: { id }, + }); + }, + + // Query procedure that returns an array of Users + listUsers: async ({ client }) => { + return await client.user.findMany(); + }, + + // Mutation procedure that creates a User + signUp: async ({ client, args: { name, role } }) => { + return await client.user.create({ + data: { + name, + role, + }, + }); + }, + + // Query procedure that returns Void + setAdmin: async ({ client, args: { userId } }) => { + await client.user.update({ + where: { id: userId }, + data: { role: 'ADMIN' }, + }); + }, + + // Query procedure that returns a custom type + getOverview: async ({ client }) => { + const userIds = await client.user.findMany({ select: { id: true } }); + const total = await client.user.count(); + return { + userIds: userIds.map((u) => u.id), + total, + roles: ['ADMIN', 'USER'], + meta: { hello: 'world' }, + }; + }, + + createMultiple: async ({ client, args: { names } }) => { + return await client.$transaction(async (tx) => { + const createdUsers: User[] = []; + for (const name of names) { + const user = await tx.user.create({ + data: { name }, + }); + createdUsers.push(user); + } + return createdUsers; + }); + }, + }, + }); + }); + + afterEach(async () => { + await client?.$disconnect(); + }); + + it('works with query proc with parameters', async () => { + // Create a user first + const created = await client.user.create({ + data: { + name: 'Alice', + role: 'USER', + }, + }); + + // Call the procedure + const result = await client.$procs.getUser({ args: { id: created.id } }); + + expect(result).toMatchObject({ + id: created.id, + name: 'Alice', + role: 'USER', + }); + }); + + it('works with query proc without parameters', async () => { + // Create multiple users + await client.user.create({ + data: { name: 'Alice', role: 'USER' }, + }); + await client.user.create({ + data: { name: 'Bob', role: 'ADMIN' }, + }); + await client.user.create({ + data: { name: 'Charlie', role: 'USER' }, + }); + + const result = await client.$procs.listUsers(); + + expect(result).toHaveLength(3); + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: 'Alice', role: 'USER' }), + expect.objectContaining({ name: 'Bob', role: 'ADMIN' }), + expect.objectContaining({ name: 'Charlie', role: 'USER' }), + ]), + ); + }); + + it('works with mutation with parameters', async () => { + const result = await client.$procs.signUp({ args: { name: 'Alice' } }); + + expect(result).toMatchObject({ + id: expect.any(Number), + name: 'Alice', + role: 'USER', + }); + + // Verify user was created in database + const users = await client.user.findMany(); + expect(users).toHaveLength(1); + expect(users[0]).toMatchObject({ + name: 'Alice', + role: 'USER', + }); + + // accepts optional role parameter + const result1 = await client.$procs.signUp({ + args: { + name: 'Bob', + role: 'ADMIN', + }, + }); + + expect(result1).toMatchObject({ + id: expect.any(Number), + name: 'Bob', + role: 'ADMIN', + }); + + // Verify user was created with correct role + const user1 = await client.user.findUnique({ + where: { id: result1.id }, + }); + expect(user1?.role).toBe('ADMIN'); + }); + + it('works with mutation proc that returns void', async () => { + // Create a regular user + const user = await client.user.create({ + data: { name: 'Alice', role: 'USER' }, + }); + + expect(user.role).toBe('USER'); + + // Call setAdmin procedure + const result = await client.$procs.setAdmin({ args: { userId: user.id } }); + + // Procedure returns void + expect(result).toBeUndefined(); + + // Verify user role was updated + const updated = await client.user.findUnique({ + where: { id: user.id }, + }); + expect(updated?.role).toBe('ADMIN'); + }); + + it('works with procedure returning custom type', async () => { + await client.user.create({ data: { name: 'Alice', role: 'USER' } }); + await client.user.create({ data: { name: 'Bob', role: 'ADMIN' } }); + + const result = await client.$procs.getOverview(); + expect(result.total).toBe(2); + expect(result.userIds).toHaveLength(2); + expect(result.roles).toEqual(expect.arrayContaining(['ADMIN', 'USER'])); + expect(result.meta).toEqual({ hello: 'world' }); + }); + + it('works with transactional mutation procs', async () => { + // unique constraint violation should rollback the transaction + await expect(client.$procs.createMultiple({ args: { names: ['Alice', 'Alice'] } })).rejects.toThrow(); + await expect(client.user.count()).resolves.toBe(0); + + // successful transaction + await expect(client.$procs.createMultiple({ args: { names: ['Alice', 'Bob'] } })).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: 'Alice' }), + expect.objectContaining({ name: 'Bob' }), + ]), + ); + }); + + it('respects the outer transaction context', async () => { + // outer client creates a transaction + await expect( + client.$transaction(async (tx) => { + await tx.$procs.signUp({ args: { name: 'Alice' } }); + await tx.$procs.signUp({ args: { name: 'Alice' } }); + }), + ).rejects.toThrow(); + await expect(client.user.count()).resolves.toBe(0); + + // without transaction + await client.$procs.signUp({ args: { name: 'Alice' } }); + await expect(client.$procs.signUp({ args: { name: 'Alice' } })).rejects.toThrow(); + await expect(client.user.count()).resolves.toBe(1); + }); +}); diff --git a/tests/e2e/orm/policy/crud/field-level.test.ts b/tests/e2e/orm/policy/crud/field-level.test.ts new file mode 100644 index 000000000..eb9ba2ea8 --- /dev/null +++ b/tests/e2e/orm/policy/crud/field-level.test.ts @@ -0,0 +1,594 @@ +import { createPolicyTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('field-level policy tests', () => { + describe('mixin tests', () => { + it('inherits field-level policies from mixins', async () => { + const db = await createPolicyTestClient( + ` + type Auth { + id Int + admin Boolean + role String + @@auth + } + + type SecureFields { + secretData String @allow('read', auth().admin) @allow('update', auth().admin) + publicData String + } + + type AuditFields { + createdAt DateTime @default(now()) + createdBy String @allow('read', auth() != null) @deny('update', true) + } + + model User { + id Int @id @default(autoincrement()) + document Document? + + @@allow('all', true) + } + + model Document with SecureFields AuditFields { + id Int @id @default(autoincrement()) + title String + owner User? @relation(fields: [ownerId], references: [id]) + ownerId Int? @unique + + @@allow('all', true) + } + + model Report with SecureFields { + id Int @id @default(autoincrement()) + name String + sensitiveInfo String @allow('read', auth().role == 'MANAGER') @allow('update', auth().role == 'MANAGER') + + @@allow('all', true) + } + `, + ); + + // Create test data without policies + await db.user.create({ data: { id: 1 } }); + await db.user.create({ data: { id: 2 } }); + await db.user.create({ data: { id: 3 } }); + + await db.$unuseAll().document.create({ + data: { + id: 1, + title: 'Doc 1', + secretData: 'SECRET', + publicData: 'PUBLIC', + createdBy: 'user1', + ownerId: 1, + }, + }); + + await db.$unuseAll().report.create({ + data: { + id: 1, + name: 'Report 1', + secretData: 'REPORT_SECRET', + publicData: 'REPORT_PUBLIC', + sensitiveInfo: 'SENSITIVE', + }, + }); + + let r; + + // Test with anonymous user (no auth) + const anonDb = db; + + r = await anonDb.document.findUnique({ where: { id: 1 } }); + expect(r.secretData).toBeNull(); // inherited from SecureFields mixin + expect(r.publicData).toEqual('PUBLIC'); + expect(r.createdBy).toBeNull(); // inherited from AuditFields mixin + expect(r.title).toEqual('Doc 1'); + + r = await anonDb.report.findUnique({ where: { id: 1 } }); + expect(r.secretData).toBeNull(); // inherited from SecureFields mixin + expect(r.publicData).toEqual('REPORT_PUBLIC'); + expect(r.sensitiveInfo).toBeNull(); // Report's own field policy + + // Test with regular authenticated user + const userDb = db.$setAuth({ id: 1, admin: false, role: 'USER' }); + + r = await userDb.document.findUnique({ where: { id: 1 } }); + expect(r.secretData).toBeNull(); // not admin + expect(r.publicData).toEqual('PUBLIC'); + expect(r.createdBy).toEqual('user1'); // authenticated + expect(r.title).toEqual('Doc 1'); + + r = await userDb.report.findUnique({ where: { id: 1 } }); + expect(r.secretData).toBeNull(); // not admin + expect(r.publicData).toEqual('REPORT_PUBLIC'); + expect(r.sensitiveInfo).toBeNull(); // not MANAGER + + // Test with admin user + const adminDb = db.$setAuth({ id: 2, admin: true, role: 'ADMIN' }); + + r = await adminDb.document.findUnique({ where: { id: 1 } }); + expect(r.secretData).toEqual('SECRET'); // admin can read + expect(r.publicData).toEqual('PUBLIC'); + expect(r.createdBy).toEqual('user1'); + expect(r.title).toEqual('Doc 1'); + + r = await adminDb.report.findUnique({ where: { id: 1 } }); + expect(r.secretData).toEqual('REPORT_SECRET'); // admin can read + expect(r.publicData).toEqual('REPORT_PUBLIC'); + expect(r.sensitiveInfo).toBeNull(); // not MANAGER + + // Test with manager user + const managerDb = db.$setAuth({ id: 3, admin: false, role: 'MANAGER' }); + + r = await managerDb.report.findUnique({ where: { id: 1 } }); + expect(r.secretData).toBeNull(); // not admin + expect(r.publicData).toEqual('REPORT_PUBLIC'); + expect(r.sensitiveInfo).toEqual('SENSITIVE'); // MANAGER can read + + // Test with select queries + r = await anonDb.document.findUnique({ + where: { id: 1 }, + select: { secretData: true, publicData: true, createdBy: true }, + }); + expect(r.secretData).toBeNull(); + expect(r.publicData).toEqual('PUBLIC'); + expect(r.createdBy).toBeNull(); + + r = await adminDb.document.findUnique({ + where: { id: 1 }, + select: { secretData: true, publicData: true, createdBy: true }, + }); + expect(r.secretData).toEqual('SECRET'); + expect(r.publicData).toEqual('PUBLIC'); + expect(r.createdBy).toEqual('user1'); + + // Test with query builder + await expect( + anonDb.$qb.selectFrom('Document').selectAll().where('id', '=', 1).executeTakeFirst(), + ).resolves.toMatchObject({ + id: 1, + title: 'Doc 1', + secretData: null, + publicData: 'PUBLIC', + createdBy: null, + }); + + await expect( + adminDb.$qb.selectFrom('Document').selectAll().where('id', '=', 1).executeTakeFirst(), + ).resolves.toMatchObject({ + id: 1, + title: 'Doc 1', + secretData: 'SECRET', + publicData: 'PUBLIC', + createdBy: 'user1', + }); + + // Test create operations with read-back + r = await anonDb.document.create({ + data: { + id: 2, + title: 'Doc 2', + secretData: 'SECRET2', + publicData: 'PUBLIC2', + createdBy: 'user2', + ownerId: 2, + }, + }); + expect(r.secretData).toBeNull(); // created but can't read back + expect(r.createdBy).toBeNull(); + expect(r.publicData).toEqual('PUBLIC2'); + + r = await adminDb.document.create({ + data: { + id: 3, + title: 'Doc 3', + secretData: 'SECRET3', + publicData: 'PUBLIC3', + createdBy: 'user3', + ownerId: 3, + }, + }); + expect(r.secretData).toEqual('SECRET3'); // admin can read back + expect(r.createdBy).toEqual('user3'); + expect(r.publicData).toEqual('PUBLIC3'); + + // Test update operations with inherited field-level policies + + // Non-admin cannot update secretData (inherited from SecureFields mixin) + await expect( + userDb.document.update({ + where: { id: 1 }, + data: { secretData: 'UPDATED_SECRET' }, + }), + ).toBeRejectedByPolicy(); + + // Non-admin can update publicData (no restrictions) + await expect( + userDb.document.update({ + where: { id: 1 }, + data: { publicData: 'UPDATED_PUBLIC' }, + }), + ).toResolveTruthy(); + + // No one can update createdBy (deny policy from AuditFields mixin) + await expect( + adminDb.document.update({ + where: { id: 1 }, + data: { createdBy: 'UPDATED_USER' }, + }), + ).toBeRejectedByPolicy(); + + // Admin can update secretData (inherited policy from SecureFields mixin) + r = await adminDb.document.update({ + where: { id: 1 }, + data: { secretData: 'ADMIN_UPDATED_SECRET' }, + }); + expect(r.secretData).toEqual('ADMIN_UPDATED_SECRET'); + expect(r.publicData).toEqual('UPDATED_PUBLIC'); // from previous update + + // Verify that updating only allowed fields works + await expect( + userDb.document.update({ + where: { id: 1 }, + data: { title: 'Updated Title' }, + }), + ).toResolveTruthy(); + + // Test with Report model - combine inherited and model-specific update policies + await db.$unuseAll().report.update({ + where: { id: 1 }, + data: { sensitiveInfo: 'ORIGINAL_SENSITIVE' }, + }); + + // Non-manager cannot update sensitiveInfo (model-specific policy) + await expect( + userDb.report.update({ + where: { id: 1 }, + data: { sensitiveInfo: 'UPDATED_SENSITIVE' }, + }), + ).toBeRejectedByPolicy(); + + // Non-admin cannot update secretData (inherited policy) + await expect( + managerDb.report.update({ + where: { id: 1 }, + data: { secretData: 'MANAGER_UPDATED_SECRET' }, + }), + ).toBeRejectedByPolicy(); + + // Manager can update sensitiveInfo but not secretData + await expect( + managerDb.report.update({ + where: { id: 1 }, + data: { sensitiveInfo: 'MANAGER_UPDATED_SENSITIVE' }, + }), + ).toResolveTruthy(); + + // Admin can update secretData but not sensitiveInfo (not MANAGER) + r = await adminDb.report.update({ + where: { id: 1 }, + data: { secretData: 'ADMIN_UPDATED_REPORT_SECRET' }, + }); + expect(r.secretData).toEqual('ADMIN_UPDATED_REPORT_SECRET'); + + // Trying to update both secretData and sensitiveInfo requires both permissions + await expect( + managerDb.report.update({ + where: { id: 1 }, + data: { secretData: 'SECRET', sensitiveInfo: 'SENSITIVE' }, + }), + ).toBeRejectedByPolicy(); // manager doesn't have permission for secretData + + await expect( + adminDb.report.update({ + where: { id: 1 }, + data: { secretData: 'SECRET', sensitiveInfo: 'SENSITIVE' }, + }), + ).toBeRejectedByPolicy(); // admin doesn't have permission for sensitiveInfo + + // Test nested updates + // (User already created at the beginning of the test) + + // Nested update respects inherited field-level policies + await expect( + userDb.user.update({ + where: { id: 1 }, + data: { + document: { + update: { + where: { id: 1 }, + data: { secretData: 'NESTED_UPDATE' }, + }, + }, + }, + }), + ).toBeRejectedByPolicy(); + + await expect( + adminDb.user.update({ + where: { id: 1 }, + data: { + document: { + update: { + where: { id: 1 }, + data: { secretData: 'ADMIN_NESTED_UPDATE' }, + }, + }, + }, + }), + ).toResolveTruthy(); + + // Test query builder updates + await expect( + userDb.$qb.updateTable('Document').set({ secretData: 'QB_UPDATE' }).where('id', '=', 2).execute(), + ).toBeRejectedByPolicy(); + + await expect( + adminDb.$qb + .updateTable('Document') + .set({ secretData: 'ADMIN_QB_UPDATE' }) + .where('id', '=', 2) + .executeTakeFirst(), + ).resolves.toMatchObject({ numUpdatedRows: 1n }); + + // createdBy cannot be updated by anyone (deny policy) + await expect( + adminDb.$qb.updateTable('Document').set({ createdBy: 'ADMIN_QB' }).where('id', '=', 2).execute(), + ).toBeRejectedByPolicy(); + }); + }); + + describe('delegate model tests', () => { + it('inherits field-level policies from delegate models', async () => { + const db = await createPolicyTestClient( + ` + type Auth { + id Int + admin Boolean + role String + @@auth + } + + model BaseContent { + id Int @id @default(autoincrement()) + title String + secretNotes String @allow('read', auth().admin) @allow('update', auth().admin) + publicContent String + createdBy String @allow('read', auth() != null) @deny('update', true) + contentType String + + @@delegate(contentType) + @@allow('all', true) + } + + model Article extends BaseContent { + body String + category String @allow('read', auth().role == 'EDITOR' || auth().admin) @allow('update', auth().role == 'EDITOR') + articleType String + + @@delegate(articleType) + } + + model BlogPost extends Article { + tags String + internalNotes String @allow('read', auth().role == 'AUTHOR') @deny('update', auth().role != 'AUTHOR') + } + `, + ); + + // Create test data + await db.$unuseAll().blogPost.create({ + data: { + id: 1, + title: 'Test Post', + secretNotes: 'SECRET', + publicContent: 'PUBLIC', + createdBy: 'user1', + body: 'Body content', + category: 'tech', + tags: 'test,blog', + internalNotes: 'INTERNAL', + }, + }); + + let r; + + // ===== READ TESTS ===== + + // Anonymous user - no auth + const anonDb = db; + + r = await anonDb.blogPost.findUnique({ where: { id: 1 } }); + expect(r.secretNotes).toBeNull(); // from BaseContent + expect(r.publicContent).toEqual('PUBLIC'); + expect(r.createdBy).toBeNull(); // from BaseContent + expect(r.category).toBeNull(); // from Article + expect(r.internalNotes).toBeNull(); // from BlogPost + expect(r.title).toEqual('Test Post'); + expect(r.body).toEqual('Body content'); + expect(r.tags).toEqual('test,blog'); + + // Regular user - authenticated but no special role + const userDb = db.$setAuth({ id: 1, admin: false, role: 'USER' }); + + r = await userDb.blogPost.findUnique({ where: { id: 1 } }); + expect(r.secretNotes).toBeNull(); // not admin + expect(r.publicContent).toEqual('PUBLIC'); + expect(r.createdBy).toEqual('user1'); // authenticated + expect(r.category).toBeNull(); // not EDITOR or admin + expect(r.internalNotes).toBeNull(); // not AUTHOR + expect(r.body).toEqual('Body content'); + + // Author user + const authorDb = db.$setAuth({ id: 2, admin: false, role: 'AUTHOR' }); + + r = await authorDb.blogPost.findUnique({ where: { id: 1 } }); + expect(r.secretNotes).toBeNull(); // not admin + expect(r.createdBy).toEqual('user1'); // authenticated + expect(r.category).toBeNull(); // not EDITOR or admin + expect(r.internalNotes).toEqual('INTERNAL'); // AUTHOR can read + expect(r.body).toEqual('Body content'); + + // Editor user + const editorDb = db.$setAuth({ id: 3, admin: false, role: 'EDITOR' }); + + r = await editorDb.blogPost.findUnique({ where: { id: 1 } }); + expect(r.secretNotes).toBeNull(); // not admin + expect(r.createdBy).toEqual('user1'); // authenticated + expect(r.category).toEqual('tech'); // EDITOR can read + expect(r.internalNotes).toBeNull(); // not AUTHOR + expect(r.body).toEqual('Body content'); + + // Admin user + const adminDb = db.$setAuth({ id: 4, admin: true, role: 'ADMIN' }); + + r = await adminDb.blogPost.findUnique({ where: { id: 1 } }); + expect(r.secretNotes).toEqual('SECRET'); // admin can read + expect(r.createdBy).toEqual('user1'); + expect(r.category).toEqual('tech'); // admin can read + expect(r.internalNotes).toBeNull(); // not AUTHOR + expect(r.publicContent).toEqual('PUBLIC'); + expect(r.body).toEqual('Body content'); + + // Test reading from base model access point + r = await anonDb.baseContent.findUnique({ where: { id: 1 } }); + expect(r.secretNotes).toBeNull(); + expect(r.createdBy).toBeNull(); + + r = await adminDb.baseContent.findUnique({ where: { id: 1 } }); + expect(r.secretNotes).toEqual('SECRET'); + expect(r.createdBy).toEqual('user1'); + + // Test reading from intermediate delegate (Article) + r = await editorDb.article.findUnique({ where: { id: 1 } }); + expect(r.secretNotes).toBeNull(); // from base, needs admin + expect(r.category).toEqual('tech'); // Article field, EDITOR can read + expect(r.createdBy).toEqual('user1'); // from base, authenticated can read + + // ===== UPDATE TESTS ===== + + // Non-admin cannot update secretNotes (from BaseContent) + await expect( + userDb.blogPost.update({ + where: { id: 1 }, + data: { secretNotes: 'UPDATED_SECRET' }, + }), + ).toBeRejectedByPolicy(); + + // No one can update createdBy (deny policy from BaseContent) + await expect( + adminDb.blogPost.update({ + where: { id: 1 }, + data: { createdBy: 'UPDATED_USER' }, + }), + ).toBeRejectedByPolicy(); + + // Admin can update secretNotes + r = await adminDb.blogPost.update({ + where: { id: 1 }, + data: { secretNotes: 'ADMIN_UPDATED_SECRET' }, + }); + expect(r.secretNotes).toEqual('ADMIN_UPDATED_SECRET'); + + // Non-editor cannot update category (from Article) + await expect( + userDb.blogPost.update({ + where: { id: 1 }, + data: { category: 'science' }, + }), + ).toBeRejectedByPolicy(); + + // Editor can update category + r = await editorDb.blogPost.update({ + where: { id: 1 }, + data: { category: 'science' }, + }); + expect(r.category).toEqual('science'); + + // Non-author cannot update internalNotes (from BlogPost) + await expect( + editorDb.blogPost.update({ + where: { id: 1 }, + data: { internalNotes: 'EDITOR_UPDATE' }, + }), + ).toBeRejectedByPolicy(); + + // Author can update internalNotes + r = await authorDb.blogPost.update({ + where: { id: 1 }, + data: { internalNotes: 'AUTHOR_UPDATED' }, + }); + expect(r.internalNotes).toEqual('AUTHOR_UPDATED'); + + // Test multi-field update requiring multiple permissions + await expect( + editorDb.blogPost.update({ + where: { id: 1 }, + data: { secretNotes: 'SECRET', category: 'tech' }, + }), + ).toBeRejectedByPolicy(); // editor can't update secretNotes + + await expect( + authorDb.blogPost.update({ + where: { id: 1 }, + data: { category: 'tech', internalNotes: 'INTERNAL' }, + }), + ).toBeRejectedByPolicy(); // author can't update category + + // Admin can update secretNotes but not internalNotes + await expect( + adminDb.blogPost.update({ + where: { id: 1 }, + data: { secretNotes: 'SECRET', internalNotes: 'ADMIN_NOTES' }, + }), + ).toBeRejectedByPolicy(); // admin is not AUTHOR + + // Test updating via base model access point + await expect( + userDb.baseContent.update({ + where: { id: 1 }, + data: { secretNotes: 'USER_SECRET' }, + }), + ).toBeRejectedByPolicy(); + + r = await adminDb.baseContent.update({ + where: { id: 1 }, + data: { secretNotes: 'BASE_ADMIN_SECRET' }, + }); + expect(r.secretNotes).toEqual('BASE_ADMIN_SECRET'); + + // Test updating via intermediate delegate (Article) + await expect( + userDb.article.update({ + where: { id: 1 }, + data: { category: 'news' }, + }), + ).toBeRejectedByPolicy(); + + r = await editorDb.article.update({ + where: { id: 1 }, + data: { category: 'news' }, + }); + expect(r.category).toEqual('news'); + + // Test with select queries + r = await anonDb.blogPost.findUnique({ + where: { id: 1 }, + select: { secretNotes: true, category: true, internalNotes: true }, + }); + expect(r.secretNotes).toBeNull(); + expect(r.category).toBeNull(); + expect(r.internalNotes).toBeNull(); + + r = await adminDb.blogPost.findUnique({ + where: { id: 1 }, + select: { secretNotes: true, category: true, createdBy: true }, + }); + expect(r.secretNotes).toEqual('BASE_ADMIN_SECRET'); + expect(r.category).toEqual('news'); + expect(r.createdBy).toEqual('user1'); + }); + }); +}); diff --git a/tests/e2e/orm/policy/migrated/field-level-policy.test.ts b/tests/e2e/orm/policy/migrated/field-level-policy.test.ts new file mode 100644 index 000000000..429b0f70e --- /dev/null +++ b/tests/e2e/orm/policy/migrated/field-level-policy.test.ts @@ -0,0 +1,1766 @@ +import { createPolicyTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('field-level policy tests migrated from v2', () => { + describe('read tests', () => { + it('works with read rules', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + admin Boolean @default(false) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('read', x > 0) + z Int @deny('read', x <= 0) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ data: { id: 1, admin: true } }); + + let r; + + // y and z are unreadable + + // create read-back + r = await db.model.create({ + data: { id: 1, x: 0, y: 0, z: 0, ownerId: 1 }, + }); + expect(r.x).toEqual(0); + expect(r.y).toBeNull(); + expect(r.z).toBeNull(); + + // update read-back + r = await db.model.update({ where: { id: 1 }, data: { x: -1 } }); + expect(r).toMatchObject({ x: -1, y: null, z: null }); + + // delete read-back + r = await db.model.delete({ where: { id: 1 } }); + expect(r).toMatchObject({ x: -1, y: null, z: null }); + + // recreate for further tests + await db.model.create({ + data: { id: 1, x: 0, y: 0, z: 0, ownerId: 1 }, + }); + + r = await db.model.findUnique({ where: { id: 1 } }); + expect(r.y).toBeNull(); + expect(r.z).toBeNull(); + r = await db.user.findUnique({ where: { id: 1 }, select: { models: true } }); + expect(r.models[0].y).toBeNull(); + expect(r.models[0].z).toBeNull(); + + r = await db.user.findUnique({ where: { id: 1 }, select: { models: { select: { y: true } } } }); + expect(r.models[0].y).toBeNull(); + expect(r.models[0].z).toBeUndefined(); + + r = await db.user.findUnique({ where: { id: 1 }, include: { models: true } }); + expect(r.models[0].y).toBeNull(); + expect(r.models[0].z).toBeNull(); + + r = await db.user.findUnique({ where: { id: 1 }, select: { models: { select: { y: true } } } }); + expect(r.models[0].y).toBeNull(); + expect(r.models[0].z).toBeUndefined(); + + r = await db.model.findUnique({ select: { x: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); + + r = await db.model.findUnique({ select: { y: true }, where: { id: 1 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toBeNull(); + expect(r.z).toBeUndefined(); + + r = await db.model.findUnique({ select: { x: false, y: true }, where: { id: 1 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toBeNull(); + expect(r.z).toBeUndefined(); + + r = await db.model.findUnique({ select: { x: true, y: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.y).toBeNull(); + expect(r.z).toBeUndefined(); + + r = await db.model.findUnique({ include: { owner: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.owner).toBeTruthy(); + expect(r.y).toBeNull(); + expect(r.z).toBeNull(); + + // y is readable + + r = await db.model.create({ + data: { id: 2, x: 1, y: 2, z: 2, ownerId: 1 }, + }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 2, z: 2 })); + + r = await db.model.findUnique({ where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 2, z: 2 })); + + r = await db.user.findUnique({ where: { id: 1 }, select: { models: { where: { id: 2 } } } }); + expect(r.models[0]).toEqual(expect.objectContaining({ x: 1, y: 2, z: 2 })); + + r = await db.user.findUnique({ + where: { id: 1 }, + select: { models: { where: { id: 2 }, select: { y: true, z: true } } }, + }); + expect(r.models[0]).toEqual(expect.objectContaining({ y: 2, z: 2 })); + + r = await db.user.findUnique({ where: { id: 1 }, select: { models: { where: { id: 2 } } } }); + expect(r.models[0]).toEqual(expect.objectContaining({ x: 1, y: 2, z: 2 })); + + r = await db.user.findUnique({ + where: { id: 1 }, + select: { models: { where: { id: 2 }, select: { y: true } } }, + }); + expect(r.models[0]).toEqual(expect.objectContaining({ y: 2 })); + r = await db.model.findUnique({ select: { x: true }, where: { id: 2 } }); + expect(r.x).toEqual(1); + expect(r.y).toBeUndefined(); + expect(r.z).toBeUndefined(); + r = await db.model.findUnique({ select: { y: true }, where: { id: 2 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toEqual(2); + expect(r.z).toBeUndefined(); + + r = await db.model.findUnique({ select: { x: false, y: true, z: true }, where: { id: 2 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toEqual(2); + expect(r.z).toEqual(2); + + r = await db.model.findUnique({ select: { x: true, y: true, z: true }, where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 2, z: 2 })); + + r = await db.model.findUnique({ include: { owner: true }, where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 2, z: 2 })); + expect(r.owner).toBeTruthy(); + + // count + await expect(db.model.count({ select: { x: true, y: true, z: true } })).resolves.toMatchObject({ + x: 2, + y: 1, + z: 1, + }); + + // aggregate + await expect(db.model.aggregate({ _min: { y: true, z: true } })).resolves.toMatchObject({ + _min: { y: 2, z: 2 }, + }); + }); + + it('works with model-level and field-level read rules', async () => { + const db = await createPolicyTestClient( + ` + model Model { + id Int @id @default(autoincrement()) + x Int @allow('read', x > 1) + @@allow('create', true) + @@allow('read', x > 0) + } + `, + ); + + await db.$unuseAll().model.create({ data: { id: 1, x: 0 } }); + await expect(db.model.count()).resolves.toEqual(0); + + await db.$unuseAll().model.create({ data: { id: 2, x: 1 } }); + const r = await db.model.findFirst(); + expect(r).toBeTruthy(); + expect(r.x).toBeNull(); + }); + + // TODO: field-level policy override + it.skip('works with read override', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + admin Boolean @default(false) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('read', x > 0, true) + owner User @relation(fields: [ownerId], references: [id]) @allow('read', x > 1, true) + ownerId Int + + @@allow('create', true) + @@allow('read', x > 1) + } + `, + ); + + await db.user.create({ data: { id: 1, admin: true } }); + + // created but can't read back + await expect( + db.model.create({ + data: { id: 1, x: 0, y: 0, ownerId: 1 }, + }), + ).toBeRejectedByPolicy(); + + // y is readable through override + // created but can't read back + await expect( + db.model.create({ + data: { id: 2, x: 1, y: 0, ownerId: 1 }, + }), + ).toBeRejectedByPolicy(); + + // y can be read back + await expect( + db.model.create({ + data: { id: 3, x: 1, y: 0, ownerId: 1 }, + select: { y: true }, + }), + ).resolves.toEqual({ y: 0 }); + + await expect(db.model.findUnique({ where: { id: 3 } })).resolves.toBeNull(); + await expect(db.model.findUnique({ where: { id: 3 }, select: { y: true } })).resolves.toEqual({ y: 0 }); + await expect(db.model.findUnique({ where: { id: 3 }, select: { x: true, y: true } })).resolves.toBeNull(); + await expect( + db.model.findUnique({ where: { id: 3 }, select: { owner: true, y: true } }), + ).resolves.toBeNull(); + await expect(db.model.findUnique({ where: { id: 3 }, include: { owner: true } })).resolves.toBeNull(); + + // y and owner are readable through override + await expect( + db.model.create({ + data: { id: 4, x: 2, y: 0, ownerId: 1 }, + select: { y: true }, + }), + ).resolves.toEqual({ y: 0 }); + await expect( + db.model.findUnique({ where: { id: 4 }, select: { owner: true, y: true } }), + ).resolves.toMatchObject({ + owner: expect.objectContaining({ admin: true }), + y: 0, + }); + await expect(db.model.findUnique({ where: { id: 4 }, include: { owner: true } })).resolves.toMatchObject({ + owner: expect.objectContaining({ admin: true }), + y: 0, + }); + }); + + it('works with read filter with auth', async () => { + const _db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + admin Boolean @default(false) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('read', auth().admin) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await _db.user.create({ data: { id: 1, admin: true } }); + + let db = _db.$setAuth({ id: 1, admin: false }); + let r; + + // y is unreadable + + r = await db.model.create({ + data: { + id: 1, + x: 0, + y: 0, + ownerId: 1, + }, + }); + expect(r.x).toEqual(0); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ where: { id: 1 } }); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ select: { x: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.y).toBeUndefined(); + + r = await db.model.findUnique({ select: { y: true }, where: { id: 1 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ select: { x: false, y: true }, where: { id: 1 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ select: { x: true, y: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ include: { owner: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.owner).toBeTruthy(); + expect(r.y).toBeNull(); + + // y is readable + db = _db.$setAuth({ id: 1, admin: true }); + r = await db.model.create({ + data: { + id: 2, + x: 1, + y: 0, + ownerId: 1, + }, + }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + + r = await db.model.findUnique({ where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + + r = await db.model.findUnique({ select: { x: true }, where: { id: 2 } }); + expect(r.x).toEqual(1); + expect(r.y).toBeUndefined(); + + r = await db.model.findUnique({ select: { y: true }, where: { id: 2 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toEqual(0); + + r = await db.model.findUnique({ select: { x: false, y: true }, where: { id: 2 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toEqual(0); + + r = await db.model.findUnique({ select: { x: true, y: true }, where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + + r = await db.model.findUnique({ include: { owner: true }, where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + expect(r.owner).toBeTruthy(); + }); + + it('works with read filter with relation', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + admin Boolean @default(false) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('read', owner.admin) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ data: { id: 1, admin: false } }); + await db.user.create({ data: { id: 2, admin: true } }); + + let r; + + // y is unreadable + + r = await db.model.create({ + data: { + id: 1, + x: 0, + y: 0, + ownerId: 1, + }, + }); + expect(r.x).toEqual(0); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ where: { id: 1 } }); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ select: { x: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.y).toBeUndefined(); + + r = await db.model.findUnique({ select: { y: true }, where: { id: 1 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ select: { x: false, y: true }, where: { id: 1 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ select: { x: true, y: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.y).toBeNull(); + + r = await db.model.findUnique({ include: { owner: true }, where: { id: 1 } }); + expect(r.x).toEqual(0); + expect(r.owner).toBeTruthy(); + expect(r.y).toBeNull(); + + // y is readable + r = await db.model.create({ + data: { + id: 2, + x: 1, + y: 0, + ownerId: 2, + }, + }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + + r = await db.model.findUnique({ where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + + r = await db.model.findUnique({ select: { x: true }, where: { id: 2 } }); + expect(r.x).toEqual(1); + expect(r.y).toBeUndefined(); + + r = await db.model.findUnique({ select: { y: true }, where: { id: 2 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toEqual(0); + + r = await db.model.findUnique({ select: { x: false, y: true }, where: { id: 2 } }); + expect(r.x).toBeUndefined(); + expect(r.y).toEqual(0); + + r = await db.model.findUnique({ select: { x: true, y: true }, where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + + r = await db.model.findUnique({ include: { owner: true }, where: { id: 2 } }); + expect(r).toEqual(expect.objectContaining({ x: 1, y: 0 })); + expect(r.owner).toBeTruthy(); + }); + + it('works with using fk policies to restrict relation reads', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + admin Boolean @default(false) + posts Post[] + + @@allow('all', true) + } + + model Post { + id Int @id @default(autoincrement()) + title String + authorId Int @allow('read', auth().admin) + author User @relation(fields: [authorId], references: [id]) + + @@allow('all', true) + } + `, + ); + + await db.user.create({ data: { id: 1, admin: false } }); + await db.user.create({ data: { id: 2, admin: true } }); + + await db.$unuseAll().post.create({ + data: { id: 1, title: 'Post 1', authorId: 1 }, + }); + + let r; + + // Non-admin user: authorId is unreadable, which prevents relation from being fetched + const nonAdminDb = db.$setAuth({ id: 1, admin: false }); + + r = await nonAdminDb.post.findUnique({ where: { id: 1 }, include: { author: true } }); + expect(r.authorId).toBeNull(); + expect(r.author).toBeNull(); // author is null because FK is not readable + + r = await nonAdminDb.post.findUnique({ where: { id: 1 }, select: { author: true } }); + expect(r.author).toBeNull(); + + // Selecting only the author field with nested select + r = await nonAdminDb.post.findUnique({ + where: { id: 1 }, + select: { author: { select: { id: true } } }, + }); + expect(r.author).toBeNull(); + + // Admin user: authorId is readable, so relation can be fetched + const adminDb = db.$setAuth({ id: 2, admin: true }); + + r = await adminDb.post.findUnique({ where: { id: 1 }, include: { author: true } }); + expect(r.authorId).toEqual(1); + expect(r.author).toMatchObject({ id: 1, admin: false }); + + r = await adminDb.post.findUnique({ where: { id: 1 }, select: { author: true } }); + expect(r.author).toMatchObject({ id: 1, admin: false }); + + r = await adminDb.post.findUnique({ + where: { id: 1 }, + select: { author: { select: { id: true, admin: true } } }, + }); + expect(r.author).toMatchObject({ id: 1, admin: false }); + + // Test with query builder + await expect( + nonAdminDb.$qb + .selectFrom('Post') + .leftJoin('User', 'User.id', 'Post.authorId') + .select(['Post.id', 'Post.authorId', 'User.id as userId']) + .where('Post.id', '=', 1) + .executeTakeFirst(), + ).resolves.toMatchObject({ id: 1, authorId: null, userId: null }); + + await expect( + adminDb.$qb + .selectFrom('Post') + .leftJoin('User', 'User.id', 'Post.authorId') + .select(['Post.id', 'Post.authorId', 'User.id as userId']) + .where('Post.id', '=', 1) + .executeTakeFirst(), + ).resolves.toMatchObject({ id: 1, authorId: 1, userId: 1 }); + }); + + it('works with all ORM find APIs', async () => { + const db = await createPolicyTestClient( + ` + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('read', x > 0) + + @@allow('all', true) + } + `, + ); + let r; + + // y is unreadable + await db.model.create({ + data: { + id: 1, + x: 0, + y: 0, + }, + }); + + r = await db.model.findUnique({ where: { id: 1 } }); + expect(r.y).toBeNull(); + + r = await db.model.findUniqueOrThrow({ where: { id: 1 } }); + expect(r.y).toBeNull(); + + r = await db.model.findFirst({ where: { id: 1 } }); + expect(r.y).toBeNull(); + + r = await db.model.findFirstOrThrow({ where: { id: 1 } }); + expect(r.y).toBeNull(); + + await db.model.create({ + data: { + id: 2, + x: 1, + y: 0, + }, + }); + r = await db.model.findMany({ where: { x: { gte: 0 } } }); + expect(r[0].y).toBeNull(); + expect(r[1].y).toEqual(0); + }); + + it('works with query builder', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + admin Boolean @default(false) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('read', owner.admin) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ data: { id: 1, admin: false } }); + await db.user.create({ data: { id: 2, admin: true } }); + + await db.$unuseAll().model.create({ + data: { id: 1, x: 1, y: 1, ownerId: 1 }, + }); + + await db.$unuseAll().model.create({ + data: { id: 2, x: 2, y: 2, ownerId: 2 }, + }); + + await expect( + db.$qb.selectFrom('Model').selectAll().where('id', '=', 1).executeTakeFirst(), + ).resolves.toMatchObject({ x: 1, y: null }); + await expect( + db.$qb.selectFrom('Model').selectAll().where('id', '=', 2).executeTakeFirst(), + ).resolves.toMatchObject({ x: 2, y: 2 }); + + await expect( + db.$qb + .selectFrom('User') + .leftJoin('Model as m', 'm.ownerId', 'User.id') + .select(['User.id', 'm.x', 'm.y']) + .where('User.id', '=', 1) + .executeTakeFirst(), + ).resolves.toEqual({ id: 1, x: 1, y: null }); + + await expect( + db.$qb + .selectFrom('User') + .leftJoin('Model as m', 'm.ownerId', 'User.id') + .select(['User.id', 'm.x', 'm.y']) + .where('User.id', '=', 2) + .executeTakeFirst(), + ).resolves.toEqual({ id: 2, x: 2, y: 2 }); + }); + + it('rejects field-level policies on relation fields', async () => { + await expect( + createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + admin Boolean @default(false) + posts Post[] @allow('read', admin) + + @@allow('all', true) + } + + model Post { + id Int @id @default(autoincrement()) + author User? @relation(fields: [authorId], references: [id]) @allow('read', author.admin) + authorId Int @allow('read', author.admin) + + @@allow('all', true) + } + `, + ), + ).rejects.toThrow(/Field-level policies are not allowed for relation fields/); + }); + + it('evaluates computed field to null when based on non-readable field', async () => { + const db = await createPolicyTestClient( + ` + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('read', x > 0) + incY Int @computed + + @@allow('all', true) + } + `, + { + computedFields: { + Model: { + incY: (eb: any) => eb('y', '+', 1), + }, + }, + } as any, + ); + + // y is unreadable, so computed field based on it should be null + await db.model.create({ + data: { id: 1, x: 0, y: 5 }, + }); + + let r = await db.model.findUnique({ where: { id: 1 } }); + expect(r.y).toBeNull(); + expect(r.incY).toBeNull(); + + r = await db.model.findUnique({ where: { id: 1 }, select: { incY: true } }); + expect(r.incY).toBeNull(); + + // y is readable, so computed field should also be readable + await db.model.create({ + data: { id: 2, x: 1, y: 5 }, + }); + + r = await db.model.findUnique({ where: { id: 2 } }); + expect(r.y).toEqual(5); + expect(r.incY).toEqual(6); + + r = await db.model.findUnique({ where: { id: 2 }, select: { incY: true } }); + expect(r.incY).toEqual(6); + }); + + it('evaluates query builder synthesized selection to null when based on non-readable field', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + admin Boolean @default(false) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('read', owner.admin) + z String @allow('read', owner.admin) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ data: { id: 1, admin: false } }); + await db.user.create({ data: { id: 2, admin: true } }); + + await db.$unuseAll().model.create({ + data: { id: 1, x: 10, y: 20, z: 'hello', ownerId: 1 }, + }); + + await db.$unuseAll().model.create({ + data: { id: 2, x: 30, y: 40, z: 'world', ownerId: 2 }, + }); + + // y and z are unreadable for model #1, so: + // - direct field selection returns null + // - function calls on unreadable fields return null + await expect( + db.$qb + .selectFrom('Model') + .select((eb: any) => [eb('y', '+', 1).as('incY'), eb.fn('upper', ['z']).as('upperZ')]) + .where('id', '=', 1) + .executeTakeFirst(), + ).resolves.toMatchObject({ incY: null, upperZ: null }); + + // y and z are readable for model #2, so function calls should work + await expect( + db.$qb + .selectFrom('Model') + .select((eb: any) => [eb('y', '+', 1).as('incY'), eb.fn('upper', ['z']).as('upperZ')]) + .where('id', '=', 2) + .executeTakeFirst(), + ).resolves.toMatchObject({ incY: 41, upperZ: 'WORLD' }); + + // Test with joins - unreadable fields in synthesized selections + await expect( + db.$qb + .selectFrom('User') + .leftJoin('Model as m', 'm.ownerId', 'User.id') + .select((eb: any) => [ + 'User.id', + eb('m.y', '+', 1).as('incY'), + eb.fn('upper', ['m.z']).as('upperZ'), + ]) + .where('User.id', '=', 1) + .executeTakeFirst(), + ).resolves.toEqual({ id: 1, incY: null, upperZ: null }); + + await expect( + db.$qb + .selectFrom('User') + .leftJoin('Model as m', 'm.ownerId', 'User.id') + .select((eb: any) => [ + 'User.id', + eb('m.y', '+', 1).as('incY'), + eb.fn('upper', ['m.z']).as('upperZ'), + ]) + .where('User.id', '=', 2) + .executeTakeFirst(), + ).resolves.toEqual({ id: 2, incY: 41, upperZ: 'WORLD' }); + }); + }); + + describe('update tests', () => { + it('works with simple updates', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', x > 0) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('create,read', true) + @@allow('update', y > 0) + } + `, + ); + + await db.user.create({ + data: { id: 1 }, + }); + + await db.model.create({ + data: { id: 1, x: 0, y: 0, ownerId: 1 }, + }); + + // denied by both model-level and field-level policies + await expect( + db.model.update({ + where: { id: 1 }, + data: { y: 2 }, + }), + ).toBeRejectedNotFound(); + + await db.model.create({ + data: { id: 2, x: 0, y: 1, ownerId: 1 }, + }); + + // denied by field-level policy + await expect( + db.model.update({ + where: { id: 2 }, + data: { y: 2 }, + }), + ).toBeRejectedByPolicy(); + + // allowed when not updating y + await expect( + db.model.update({ + where: { id: 2 }, + data: { x: 2 }, + }), + ).toResolveTruthy(); + + await db.model.create({ + data: { id: 3, x: 1, y: 1, ownerId: 1 }, + }); + + // allowed when updating y + await expect( + db.model.update({ + where: { id: 3 }, + data: { y: 2 }, + }), + ).toResolveTruthy(); + }); + + // TODO: field-level policy override + it.skip('works override', async () => { + const db = await createPolicyTestClient( + ` + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', x > 0, true) @deny('update', x == 100) + z Int @allow('update', x > 1, true) + + @@allow('create,read', true) + @@allow('update', y > 0) + } + `, + ); + + await db.model.create({ + data: { id: 1, x: 0, y: 0, z: 0 }, + }); + + await expect( + db.model.update({ + where: { id: 1 }, + data: { y: 2 }, + }), + ).toBeRejectedByPolicy(); + await expect( + db.model.update({ + where: { id: 1 }, + data: { x: 2 }, + }), + ).toBeRejectedByPolicy(); + + await db.model.create({ + data: { id: 2, x: 1, y: 0, z: 0 }, + }); + await expect( + db.model.update({ + where: { id: 2 }, + data: { x: 2, y: 1 }, + }), + ).toBeRejectedByPolicy(); // denied because field `x` doesn't have override + await expect( + db.model.update({ + where: { id: 2 }, + data: { y: 1, z: 1 }, + }), + ).toBeRejectedByPolicy(); // denied because field `z` override not satisfied + await expect( + db.model.update({ + where: { id: 2 }, + data: { y: 1 }, + }), + ).toResolveTruthy(); // allowed by override + await expect(db.model.findUnique({ where: { id: 2 } })).resolves.toMatchObject({ y: 1 }); + + await db.model.create({ + data: { id: 3, x: 2, y: 0, z: 0 }, + }); + await expect( + db.model.update({ + where: { id: 3 }, + data: { y: 1, z: 1 }, + }), + ).toResolveTruthy(); // allowed by override + await expect(db.model.findUnique({ where: { id: 3 } })).resolves.toMatchObject({ y: 1, z: 1 }); + + await db.model.create({ + data: { id: 4, x: 100, y: 0, z: 0 }, + }); + await expect( + db.model.update({ + where: { id: 4 }, + data: { y: 1 }, + }), + ).toBeRejectedByPolicy(); // can't be allowed by override due to field-level deny + }); + + it('works with filter with relation', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + models Model[] + admin Boolean @default(false) + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', owner.admin) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ + data: { id: 1, admin: false }, + }); + await db.user.create({ + data: { id: 2, admin: true }, + }); + + await db.model.create({ + data: { id: 1, x: 0, y: 0, ownerId: 1 }, + }); + + // rejected by y field-level policy + await expect( + db.model.update({ + where: { id: 1 }, + data: { y: 2 }, + }), + ).toBeRejectedByPolicy(); + + // allowed since not updating y + await expect( + db.model.update({ + where: { id: 1 }, + data: { x: 2 }, + }), + ).toResolveTruthy(); + + await db.model.create({ + data: { id: 2, x: 0, y: 0, ownerId: 2 }, + }); + await expect( + db.model.update({ + where: { id: 2 }, + data: { y: 2 }, + }), + ).toResolveTruthy(); + }); + + it('works with nested to-many relation', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + models Model[] + admin Boolean @default(false) + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', owner.admin) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ + data: { id: 1, admin: false, models: { create: { id: 1, x: 0, y: 0 } } }, + }); + await db.user.create({ + data: { id: 2, admin: true, models: { create: { id: 2, x: 0, y: 0 } } }, + }); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { update: { where: { id: 1 }, data: { y: 2 } } } }, + }), + ).toBeRejectedByPolicy(); + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { update: { where: { id: 1 }, data: { x: 2 } } } }, + }), + ).toResolveTruthy(); + + await expect( + db.user.update({ + where: { id: 2 }, + data: { models: { update: { where: { id: 2 }, data: { y: 2 } } } }, + }), + ).toResolveTruthy(); + }); + + it('works with nested to-one relation', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + model Model? + admin Boolean @default(false) + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', owner.admin) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int @unique + + @@allow('all', true) + } + `, + ); + + await db.user.create({ + data: { id: 1, admin: false, model: { create: { id: 1, x: 0, y: 0 } } }, + }); + await db.user.create({ + data: { id: 2, admin: true, model: { create: { id: 2, x: 0, y: 0 } } }, + }); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { update: { data: { y: 2 } } } }, + }), + ).toBeRejectedByPolicy(); + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { update: { y: 2 } } }, + }), + ).toBeRejectedByPolicy(); + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { update: { data: { x: 2 } } } }, + }), + ).toResolveTruthy(); + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { update: { x: 2 } } }, + }), + ).toResolveTruthy(); + + await expect( + db.user.update({ + where: { id: 2 }, + data: { model: { update: { data: { y: 2 } } } }, + }), + ).toResolveTruthy(); + await expect( + db.user.update({ + where: { id: 2 }, + data: { model: { update: { y: 2 } } }, + }), + ).toResolveTruthy(); + }); + + it('works with connect to-many relation', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + models Model[] + admin Boolean @default(false) + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + value Int + owner User? @relation(fields: [ownerId], references: [id]) + ownerId Int? @allow('update', value > 0) + + @@allow('all', true) + } + `, + ); + + await db.user.create({ data: { id: 1, admin: false } }); + await db.user.create({ data: { id: 2, admin: true } }); + await db.model.create({ data: { id: 1, value: 0 } }); + await db.model.create({ data: { id: 2, value: 1 } }); + + // connect/disconnect from owning side + + await expect( + db.model.update({ + where: { id: 1 }, + data: { owner: { connect: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + + // force connect + await db.$unuseAll().model.update({ + where: { id: 1 }, + data: { owner: { connect: { id: 1 } } }, + }); + + // disconnect with filter + await expect( + db.model.update({ + where: { id: 1 }, + data: { owner: { disconnect: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + + // force connect + await db.$unuseAll().model.update({ + where: { id: 1 }, + data: { owner: { connect: { id: 1 } } }, + }); + + // disconnect + await expect( + db.model.update({ + where: { id: 1 }, + data: { owner: { disconnect: true } }, + }), + ).toBeRejectedByPolicy(); + + await expect( + db.model.update({ + where: { id: 2 }, + data: { owner: { connect: { id: 1 } } }, + }), + ).toResolveTruthy(); + await expect( + db.model.update({ + where: { id: 2 }, + data: { owner: { disconnect: { id: 1 } } }, + }), + ).toResolveTruthy(); + + // connect/disconnect from non-owning side + + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { connect: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + + // force connect + await db.$unuseAll().user.update({ + where: { id: 1 }, + data: { models: { connect: { id: 1 } } }, + }); + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { disconnect: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { set: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { connect: { id: 2 } } }, + }), + ).toResolveTruthy(); + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { disconnect: { id: 2 } } }, + }), + ).toResolveTruthy(); + + // model#1 needs to be disconnected but it violates the policy + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { set: { id: 2 } } }, + }), + ).toBeRejectedByPolicy(); + + // force model#1 disconnect + await db.$unuseAll().model.update({ + where: { id: 1 }, + data: { ownerId: null }, + }); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { set: { id: 2 } } }, + }), + ).toResolveTruthy(); + }); + + it('works with connect to-one relation', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + model Model? + admin Boolean @default(false) + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + value Int + owner User? @relation(fields: [ownerId], references: [id]) + ownerId Int? @unique @allow('update', value > 0) + + @@allow('all', true) + } + `, + ); + + await db.user.create({ data: { id: 1, admin: false } }); + await db.user.create({ data: { id: 2, admin: true } }); + await db.model.create({ data: { id: 1, value: 0 } }); + await db.model.create({ data: { id: 2, value: 1 } }); + + await expect( + db.model.update({ + where: { id: 1 }, + data: { owner: { connect: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + + // force connect + await db.$unuseAll().model.update({ + where: { id: 1 }, + data: { owner: { connect: { id: 1 } } }, + }); + + await expect( + db.model.update({ + where: { id: 1 }, + data: { owner: { disconnect: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + + // force disconnect + await db.$unuseAll().model.update({ + where: { id: 1 }, + data: { owner: { disconnect: true } }, + }); + + await expect( + db.model.update({ + where: { id: 2 }, + data: { owner: { connect: { id: 1 } } }, + }), + ).toResolveTruthy(); + await expect( + db.model.update({ + where: { id: 2 }, + data: { owner: { disconnect: { id: 1 } } }, + }), + ).toResolveTruthy(); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { connect: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + await db.$unuseAll().user.update({ + where: { id: 1 }, + data: { model: { connect: { id: 1 } } }, + }); + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { disconnect: { id: 1 } } }, + }), + ).toBeRejectedByPolicy(); + + // connecting model#2 results in disconnecting model#1, which is denied by policy + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { connect: { id: 2 } } }, + }), + ).toBeRejectedByPolicy(); + + // force disconnect of model#1 + await db.$unuseAll().model.update({ + where: { id: 1 }, + data: { ownerId: null }, + }); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { connect: { id: 2 } } }, + }), + ).toResolveTruthy(); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { model: { disconnect: { id: 2 } } }, + }), + ).toResolveTruthy(); + }); + + it('works simple updateMany', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', x > 0) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ + data: { + id: 1, + models: { + create: [ + { id: 1, x: 0, y: 0 }, + { id: 2, x: 1, y: 0 }, + ], + }, + }, + }); + + await expect(db.model.updateMany({ data: { y: 2 } })).toBeRejectedByPolicy(); + await expect(db.model.findUnique({ where: { id: 1 } })).resolves.toEqual( + expect.objectContaining({ x: 0, y: 0 }), + ); + await expect(db.model.findUnique({ where: { id: 2 } })).resolves.toEqual( + expect.objectContaining({ x: 1, y: 0 }), + ); + + await expect(db.model.updateMany({ where: { x: 1 }, data: { y: 2 } })).resolves.toEqual({ count: 1 }); + await expect(db.model.findUnique({ where: { id: 1 } })).resolves.toEqual( + expect.objectContaining({ x: 0, y: 0 }), + ); + await expect(db.model.findUnique({ where: { id: 2 } })).resolves.toEqual( + expect.objectContaining({ x: 1, y: 2 }), + ); + }); + + it('works with query builder', async () => { + const db = await createPolicyTestClient( + ` + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', x > 0) + @@allow('all', true) + } + `, + ); + + await db.model.create({ data: { id: 1, x: 0, y: 0 } }); + + // y not updatable + await expect( + db.$qb.updateTable('Model').set({ y: 2 }).where('id', '=', 1).execute(), + ).toBeRejectedByPolicy(); + + // x updatable + await expect( + db.$qb.updateTable('Model').set({ x: 1 }).where('id', '=', 1).executeTakeFirst(), + ).resolves.toMatchObject({ numUpdatedRows: 1n }); + + // now y is updatable + await expect(db.$qb.updateTable('Model').set({ y: 2 }).executeTakeFirst()).resolves.toMatchObject({ + numUpdatedRows: 1n, + }); + + await expect(db.model.findUnique({ where: { id: 1 } })).resolves.toEqual( + expect.objectContaining({ x: 1, y: 2 }), + ); + }); + + // TODO: field-level policy override + it.skip('works with updateMany override', async () => { + const db = await createPolicyTestClient( + ` + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', x > 0, override: true) + + @@allow('create,read', true) + @@allow('update', x > 1) + } + `, + ); + + await db.model.create({ data: { id: 1, x: 0, y: 0 } }); + await db.model.create({ data: { id: 2, x: 1, y: 0 } }); + + await expect(db.model.updateMany({ data: { y: 2 } })).resolves.toEqual({ count: 1 }); + await expect(db.model.findUnique({ where: { id: 1 } })).resolves.toEqual( + expect.objectContaining({ x: 0, y: 0 }), + ); + await expect(db.model.findUnique({ where: { id: 2 } })).resolves.toEqual( + expect.objectContaining({ x: 1, y: 2 }), + ); + + await expect(db.model.updateMany({ data: { x: 2, y: 3 } })).resolves.toEqual({ count: 0 }); + }); + + it('works with nested updateMany', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + models Model[] + + @@allow('all', true) + } + + model Model { + id Int @id @default(autoincrement()) + x Int + y Int @allow('update', x > 0) + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ + data: { + id: 1, + models: { + create: [ + { id: 1, x: 0, y: 0 }, + { id: 2, x: 1, y: 0 }, + ], + }, + }, + }); + + await expect( + db.user.update({ where: { id: 1 }, data: { models: { updateMany: { where: {}, data: { y: 2 } } } } }), + ).toBeRejectedByPolicy(); + await expect(db.model.findUnique({ where: { id: 1 } })).resolves.toEqual( + expect.objectContaining({ x: 0, y: 0 }), + ); + await expect(db.model.findUnique({ where: { id: 2 } })).resolves.toEqual( + expect.objectContaining({ x: 1, y: 0 }), + ); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { updateMany: { where: { id: 1 }, data: { y: 2 } } } }, + }), + ).toBeRejectedByPolicy(); + await expect(db.model.findUnique({ where: { id: 1 } })).resolves.toEqual( + expect.objectContaining({ x: 0, y: 0 }), + ); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { models: { updateMany: { where: { id: 2 }, data: { y: 2 } } } }, + }), + ).toResolveTruthy(); + await expect(db.model.findUnique({ where: { id: 2 } })).resolves.toEqual( + expect.objectContaining({ x: 1, y: 2 }), + ); + }); + }); + + describe('misc tests', () => { + it('works with this expression', async () => { + const _db = await createPolicyTestClient( + ` + model User { + id Int @id + username String @allow("all", auth() == this) + @@allow('all', true) + } + `, + ); + + await _db.user.create({ data: { id: 1, username: 'test' } }); + + // admin + let r = await _db.$setAuth({ id: 1, admin: true }).user.findFirst(); + expect(r.username).toEqual('test'); + + // owner + r = await _db.$setAuth({ id: 1 }).user.findFirst(); + expect(r.username).toEqual('test'); + + // anonymous + r = await _db.user.findFirst(); + expect(r.username).toBeNull(); + + // non-owner + r = await _db.$setAuth({ id: 2 }).user.findFirst(); + expect(r.username).toBeNull(); + }); + + it('works with collection predicate', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + foos Foo[] + a Int @allow('read', foos?[x > 0 && bars![y > 0]]) + b Int @allow('read', foos^[x == 1]) + + @@allow('all', true) + } + + model Foo { + id Int @id @default(autoincrement()) + x Int + owner User @relation(fields: [ownerId], references: [id]) + ownerId Int + bars Bar[] + + @@allow('all', true) + } + + model Bar { + id Int @id @default(autoincrement()) + y Int + foo Foo @relation(fields: [fooId], references: [id]) + fooId Int + + @@allow('all', true) + } + `, + ); + + await db.user.create({ + data: { + id: 1, + a: 1, + b: 2, + foos: { + create: [ + { x: 0, bars: { create: [{ y: 1 }] } }, + { x: 1, bars: { create: [{ y: 0 }, { y: 1 }] } }, + ], + }, + }, + }); + + let r = await db.user.findUnique({ where: { id: 1 } }); + expect(r.a).toBeNull(); + expect(r.b).toBeNull(); + + await db.user.create({ + data: { + id: 2, + a: 1, + b: 2, + foos: { + create: [{ x: 2, bars: { create: [{ y: 0 }, { y: 1 }] } }], + }, + }, + }); + r = await db.user.findUnique({ where: { id: 2 } }); + expect(r.a).toBeNull(); + expect(r.b).toBe(2); + + await db.user.create({ + data: { + id: 3, + a: 1, + b: 2, + foos: { + create: [{ x: 2 }], + }, + }, + }); + r = await db.user.findUnique({ where: { id: 3 } }); + expect(r.a).toBe(1); + + await db.user.create({ + data: { + id: 4, + a: 1, + b: 2, + foos: { + create: [{ x: 2, bars: { create: [{ y: 1 }, { y: 2 }] } }], + }, + }, + }); + r = await db.user.findUnique({ where: { id: 4 } }); + expect(r.a).toBe(1); + expect(r.b).toBe(2); + }); + + it('works with deny only without field access', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + role String @deny('update', auth().role != 'ADMIN') + + @@allow('all', true) + } + `, + ); + + const user = await db.user.create({ + data: { role: 'USER' }, + }); + + await expect( + db.$setAuth({ id: 1, role: 'ADMIN' }).user.update({ + where: { id: user.id }, + data: { role: 'ADMIN' }, + }), + ).toResolveTruthy(); + + await expect( + db.$setAuth({ id: 1, role: 'USER' }).user.update({ + where: { id: user.id }, + data: { role: 'ADMIN' }, + }), + ).toBeRejectedByPolicy(); + }); + + it('works with deny only with field access', async () => { + const db = await createPolicyTestClient( + ` + model User { + id Int @id @default(autoincrement()) + locked Boolean @default(false) + role String @deny('update', auth().role != 'ADMIN' || locked) + + @@allow('all', true) + } + `, + ); + + const user1 = await db.user.create({ + data: { role: 'USER' }, + }); + + await expect( + db.$setAuth({ id: 1, role: 'ADMIN' }).user.update({ + where: { id: user1.id }, + data: { role: 'ADMIN' }, + }), + ).toResolveTruthy(); + + await expect( + db.$setAuth({ id: 1, role: 'USER' }).user.update({ + where: { id: user1.id }, + data: { role: 'ADMIN' }, + }), + ).toBeRejectedByPolicy(); + + const user2 = await db.user.create({ + data: { role: 'USER', locked: true }, + }); + + await expect( + db.$setAuth({ id: 1, role: 'ADMIN' }).user.update({ + where: { id: user2.id }, + data: { role: 'ADMIN' }, + }), + ).toBeRejectedByPolicy(); + }); + }); +}); diff --git a/tests/e2e/orm/schemas/auth-type/input.ts b/tests/e2e/orm/schemas/auth-type/input.ts index 3c7c14554..74cd690b9 100644 --- a/tests/e2e/orm/schemas/auth-type/input.ts +++ b/tests/e2e/orm/schemas/auth-type/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 FooFindManyArgs = $FindManyArgs<$Schema, "Foo">; export type FooFindUniqueArgs = $FindUniqueArgs<$Schema, "Foo">; export type FooFindFirstArgs = $FindFirstArgs<$Schema, "Foo">; +export type FooExistsArgs = $ExistsArgs<$Schema, "Foo">; export type FooCreateArgs = $CreateArgs<$Schema, "Foo">; export type FooCreateManyArgs = $CreateManyArgs<$Schema, "Foo">; export type FooCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Foo">; diff --git a/tests/e2e/orm/schemas/basic/input.ts b/tests/e2e/orm/schemas/basic/input.ts index 59e922527..4bbec22c6 100644 --- a/tests/e2e/orm/schemas/basic/input.ts +++ b/tests/e2e/orm/schemas/basic/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; @@ -51,6 +53,7 @@ export type PostGetPayload; 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">; @@ -71,6 +74,7 @@ export type CommentGetPayload; 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">; diff --git a/tests/e2e/orm/schemas/default-auth/input.ts b/tests/e2e/orm/schemas/default-auth/input.ts index 2fdb7acca..89e46711c 100644 --- a/tests/e2e/orm/schemas/default-auth/input.ts +++ b/tests/e2e/orm/schemas/default-auth/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; @@ -51,6 +53,7 @@ export type ProfileGetPayload; export type AddressFindUniqueArgs = $FindUniqueArgs<$Schema, "Address">; export type AddressFindFirstArgs = $FindFirstArgs<$Schema, "Address">; +export type AddressExistsArgs = $ExistsArgs<$Schema, "Address">; export type AddressCreateArgs = $CreateArgs<$Schema, "Address">; export type AddressCreateManyArgs = $CreateManyArgs<$Schema, "Address">; export type AddressCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Address">; diff --git a/tests/e2e/orm/schemas/delegate/input.ts b/tests/e2e/orm/schemas/delegate/input.ts index 331448486..23eb5c08b 100644 --- a/tests/e2e/orm/schemas/delegate/input.ts +++ b/tests/e2e/orm/schemas/delegate/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; @@ -51,6 +53,7 @@ export type CommentGetPayload; export type AssetFindUniqueArgs = $FindUniqueArgs<$Schema, "Asset">; export type AssetFindFirstArgs = $FindFirstArgs<$Schema, "Asset">; +export type AssetExistsArgs = $ExistsArgs<$Schema, "Asset">; export type AssetCreateArgs = $CreateArgs<$Schema, "Asset">; export type AssetCreateManyArgs = $CreateManyArgs<$Schema, "Asset">; export type AssetCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Asset">; @@ -71,6 +74,7 @@ export type AssetGetPayload; export type VideoFindUniqueArgs = $FindUniqueArgs<$Schema, "Video">; export type VideoFindFirstArgs = $FindFirstArgs<$Schema, "Video">; +export type VideoExistsArgs = $ExistsArgs<$Schema, "Video">; export type VideoCreateArgs = $CreateArgs<$Schema, "Video">; export type VideoCreateManyArgs = $CreateManyArgs<$Schema, "Video">; export type VideoCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Video">; @@ -91,6 +95,7 @@ export type VideoGetPayload; export type RatedVideoFindUniqueArgs = $FindUniqueArgs<$Schema, "RatedVideo">; export type RatedVideoFindFirstArgs = $FindFirstArgs<$Schema, "RatedVideo">; +export type RatedVideoExistsArgs = $ExistsArgs<$Schema, "RatedVideo">; export type RatedVideoCreateArgs = $CreateArgs<$Schema, "RatedVideo">; export type RatedVideoCreateManyArgs = $CreateManyArgs<$Schema, "RatedVideo">; export type RatedVideoCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "RatedVideo">; @@ -111,6 +116,7 @@ export type RatedVideoGetPayload; export type ImageFindUniqueArgs = $FindUniqueArgs<$Schema, "Image">; export type ImageFindFirstArgs = $FindFirstArgs<$Schema, "Image">; +export type ImageExistsArgs = $ExistsArgs<$Schema, "Image">; export type ImageCreateArgs = $CreateArgs<$Schema, "Image">; export type ImageCreateManyArgs = $CreateManyArgs<$Schema, "Image">; export type ImageCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Image">; @@ -131,6 +137,7 @@ export type ImageGetPayload; export type GalleryFindUniqueArgs = $FindUniqueArgs<$Schema, "Gallery">; export type GalleryFindFirstArgs = $FindFirstArgs<$Schema, "Gallery">; +export type GalleryExistsArgs = $ExistsArgs<$Schema, "Gallery">; export type GalleryCreateArgs = $CreateArgs<$Schema, "Gallery">; export type GalleryCreateManyArgs = $CreateManyArgs<$Schema, "Gallery">; export type GalleryCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Gallery">; diff --git a/tests/e2e/orm/schemas/json/input.ts b/tests/e2e/orm/schemas/json/input.ts index 3c7c14554..74cd690b9 100644 --- a/tests/e2e/orm/schemas/json/input.ts +++ b/tests/e2e/orm/schemas/json/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 FooFindManyArgs = $FindManyArgs<$Schema, "Foo">; export type FooFindUniqueArgs = $FindUniqueArgs<$Schema, "Foo">; export type FooFindFirstArgs = $FindFirstArgs<$Schema, "Foo">; +export type FooExistsArgs = $ExistsArgs<$Schema, "Foo">; export type FooCreateArgs = $CreateArgs<$Schema, "Foo">; export type FooCreateManyArgs = $CreateManyArgs<$Schema, "Foo">; export type FooCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Foo">; diff --git a/tests/e2e/orm/schemas/name-mapping/input.ts b/tests/e2e/orm/schemas/name-mapping/input.ts index 3799802f9..c6b620ee6 100644 --- a/tests/e2e/orm/schemas/name-mapping/input.ts +++ b/tests/e2e/orm/schemas/name-mapping/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; diff --git a/tests/e2e/orm/schemas/omit/input.ts b/tests/e2e/orm/schemas/omit/input.ts index bd73757d3..47ada0edd 100644 --- a/tests/e2e/orm/schemas/omit/input.ts +++ b/tests/e2e/orm/schemas/omit/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; @@ -51,6 +53,7 @@ export type PostGetPayload; export type BaseFindUniqueArgs = $FindUniqueArgs<$Schema, "Base">; export type BaseFindFirstArgs = $FindFirstArgs<$Schema, "Base">; +export type BaseExistsArgs = $ExistsArgs<$Schema, "Base">; export type BaseCreateArgs = $CreateArgs<$Schema, "Base">; export type BaseCreateManyArgs = $CreateManyArgs<$Schema, "Base">; export type BaseCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Base">; @@ -71,6 +74,7 @@ export type BaseGetPayload; export type SubFindUniqueArgs = $FindUniqueArgs<$Schema, "Sub">; export type SubFindFirstArgs = $FindFirstArgs<$Schema, "Sub">; +export type SubExistsArgs = $ExistsArgs<$Schema, "Sub">; export type SubCreateArgs = $CreateArgs<$Schema, "Sub">; export type SubCreateManyArgs = $CreateManyArgs<$Schema, "Sub">; export type SubCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Sub">; diff --git a/tests/e2e/orm/schemas/petstore/input.ts b/tests/e2e/orm/schemas/petstore/input.ts index 7beee9d5d..37afa4933 100644 --- a/tests/e2e/orm/schemas/petstore/input.ts +++ b/tests/e2e/orm/schemas/petstore/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; export type PetFindUniqueArgs = $FindUniqueArgs<$Schema, "Pet">; export type PetFindFirstArgs = $FindFirstArgs<$Schema, "Pet">; +export type PetExistsArgs = $ExistsArgs<$Schema, "Pet">; export type PetCreateArgs = $CreateArgs<$Schema, "Pet">; export type PetCreateManyArgs = $CreateManyArgs<$Schema, "Pet">; export type PetCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Pet">; @@ -51,6 +53,7 @@ export type PetGetPayload, export type OrderFindManyArgs = $FindManyArgs<$Schema, "Order">; export type OrderFindUniqueArgs = $FindUniqueArgs<$Schema, "Order">; export type OrderFindFirstArgs = $FindFirstArgs<$Schema, "Order">; +export type OrderExistsArgs = $ExistsArgs<$Schema, "Order">; export type OrderCreateArgs = $CreateArgs<$Schema, "Order">; export type OrderCreateManyArgs = $CreateManyArgs<$Schema, "Order">; export type OrderCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Order">; diff --git a/tests/e2e/orm/schemas/procedures/input.ts b/tests/e2e/orm/schemas/procedures/input.ts new file mode 100644 index 000000000..22bdbfa73 --- /dev/null +++ b/tests/e2e/orm/schemas/procedures/input.ts @@ -0,0 +1,31 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// 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, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "User", Args, Options>; diff --git a/tests/e2e/orm/schemas/procedures/models.ts b/tests/e2e/orm/schemas/procedures/models.ts new file mode 100644 index 000000000..9920c1011 --- /dev/null +++ b/tests/e2e/orm/schemas/procedures/models.ts @@ -0,0 +1,13 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// 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 Overview = $TypeDefResult<$Schema, "Overview">; +export const Role = $schema.enums.Role.values; +export type Role = (typeof Role)[keyof typeof Role]; diff --git a/tests/e2e/orm/schemas/procedures/schema.ts b/tests/e2e/orm/schemas/procedures/schema.ts new file mode 100644 index 000000000..f5a9044e5 --- /dev/null +++ b/tests/e2e/orm/schemas/procedures/schema.ts @@ -0,0 +1,121 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +export class SchemaType implements SchemaDef { + provider = { + type: "sqlite" + } as const; + models = { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + name: { + name: "name", + type: "String", + unique: true, + attributes: [{ name: "@unique" }] + }, + role: { + name: "role", + type: "Role", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal("USER") }] }], + default: "USER" + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" }, + name: { type: "String" } + } + } + } as const; + typeDefs = { + Overview: { + name: "Overview", + fields: { + userIds: { + name: "userIds", + type: "Int", + array: true + }, + total: { + name: "total", + type: "Int" + }, + roles: { + name: "roles", + type: "Role", + array: true + }, + meta: { + name: "meta", + type: "Json", + optional: true + } + } + } + } as const; + enums = { + Role: { + values: { + ADMIN: "ADMIN", + USER: "USER" + } + } + } as const; + authType = "User" as const; + procedures = { + getUser: { + params: { + id: { name: "id", type: "Int" } + }, + returnType: "User" + }, + listUsers: { + params: {}, + returnType: "User", + returnArray: true + }, + signUp: { + params: { + name: { name: "name", type: "String" }, + role: { name: "role", optional: true, type: "Role" } + }, + returnType: "User", + mutation: true + }, + setAdmin: { + params: { + userId: { name: "userId", type: "Int" } + }, + returnType: "Void", + mutation: true + }, + getOverview: { + params: {}, + returnType: "Overview" + }, + createMultiple: { + params: { + names: { name: "names", array: true, type: "String" } + }, + returnType: "User", + returnArray: true, + mutation: true + } + } as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/tests/e2e/orm/schemas/procedures/schema.zmodel b/tests/e2e/orm/schemas/procedures/schema.zmodel new file mode 100644 index 000000000..25380dab3 --- /dev/null +++ b/tests/e2e/orm/schemas/procedures/schema.zmodel @@ -0,0 +1,29 @@ +datasource db { + provider = 'sqlite' + url = 'file:./test.db' +} + +enum Role { + ADMIN + USER +} + +type Overview { + userIds Int[] + total Int + roles Role[] + meta Json? +} + +model User { + id Int @id @default(autoincrement()) + name String @unique + role Role @default(USER) +} + +procedure getUser(id: Int): User +procedure listUsers(): User[] +mutation procedure signUp(name: String, role: Role?): User +mutation procedure setAdmin(userId: Int): Void +procedure getOverview(): Overview +mutation procedure createMultiple(names: String[]): User[] diff --git a/tests/e2e/orm/schemas/todo/input.ts b/tests/e2e/orm/schemas/todo/input.ts index 66b1696a9..73614082d 100644 --- a/tests/e2e/orm/schemas/todo/input.ts +++ b/tests/e2e/orm/schemas/todo/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 SpaceFindManyArgs = $FindManyArgs<$Schema, "Space">; export type SpaceFindUniqueArgs = $FindUniqueArgs<$Schema, "Space">; export type SpaceFindFirstArgs = $FindFirstArgs<$Schema, "Space">; +export type SpaceExistsArgs = $ExistsArgs<$Schema, "Space">; export type SpaceCreateArgs = $CreateArgs<$Schema, "Space">; export type SpaceCreateManyArgs = $CreateManyArgs<$Schema, "Space">; export type SpaceCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Space">; @@ -31,6 +32,7 @@ export type SpaceGetPayload; export type SpaceUserFindUniqueArgs = $FindUniqueArgs<$Schema, "SpaceUser">; export type SpaceUserFindFirstArgs = $FindFirstArgs<$Schema, "SpaceUser">; +export type SpaceUserExistsArgs = $ExistsArgs<$Schema, "SpaceUser">; export type SpaceUserCreateArgs = $CreateArgs<$Schema, "SpaceUser">; export type SpaceUserCreateManyArgs = $CreateManyArgs<$Schema, "SpaceUser">; export type SpaceUserCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "SpaceUser">; @@ -51,6 +53,7 @@ export type SpaceUserGetPayload; 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">; @@ -71,6 +74,7 @@ export type UserGetPayload; export type ListFindUniqueArgs = $FindUniqueArgs<$Schema, "List">; export type ListFindFirstArgs = $FindFirstArgs<$Schema, "List">; +export type ListExistsArgs = $ExistsArgs<$Schema, "List">; export type ListCreateArgs = $CreateArgs<$Schema, "List">; export type ListCreateManyArgs = $CreateManyArgs<$Schema, "List">; export type ListCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "List">; @@ -91,6 +95,7 @@ export type ListGetPayload; export type TodoFindUniqueArgs = $FindUniqueArgs<$Schema, "Todo">; export type TodoFindFirstArgs = $FindFirstArgs<$Schema, "Todo">; +export type TodoExistsArgs = $ExistsArgs<$Schema, "Todo">; export type TodoCreateArgs = $CreateArgs<$Schema, "Todo">; export type TodoCreateManyArgs = $CreateManyArgs<$Schema, "Todo">; export type TodoCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Todo">; diff --git a/tests/e2e/orm/schemas/typed-json/input.ts b/tests/e2e/orm/schemas/typed-json/input.ts index 88a751d1c..22bdbfa73 100644 --- a/tests/e2e/orm/schemas/typed-json/input.ts +++ b/tests/e2e/orm/schemas/typed-json/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; diff --git a/tests/e2e/orm/schemas/typing/input.ts b/tests/e2e/orm/schemas/typing/input.ts index 5f893115f..1fa900dc1 100644 --- a/tests/e2e/orm/schemas/typing/input.ts +++ b/tests/e2e/orm/schemas/typing/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; @@ -51,6 +53,7 @@ export type PostGetPayload; 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">; @@ -71,6 +74,7 @@ export type ProfileGetPayload; export type TagFindUniqueArgs = $FindUniqueArgs<$Schema, "Tag">; export type TagFindFirstArgs = $FindFirstArgs<$Schema, "Tag">; +export type TagExistsArgs = $ExistsArgs<$Schema, "Tag">; export type TagCreateArgs = $CreateArgs<$Schema, "Tag">; export type TagCreateManyArgs = $CreateManyArgs<$Schema, "Tag">; export type TagCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Tag">; @@ -91,6 +95,7 @@ export type TagGetPayload, export type RegionFindManyArgs = $FindManyArgs<$Schema, "Region">; export type RegionFindUniqueArgs = $FindUniqueArgs<$Schema, "Region">; export type RegionFindFirstArgs = $FindFirstArgs<$Schema, "Region">; +export type RegionExistsArgs = $ExistsArgs<$Schema, "Region">; export type RegionCreateArgs = $CreateArgs<$Schema, "Region">; export type RegionCreateManyArgs = $CreateManyArgs<$Schema, "Region">; export type RegionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Region">; @@ -111,6 +116,7 @@ export type RegionGetPayload; export type MetaFindUniqueArgs = $FindUniqueArgs<$Schema, "Meta">; export type MetaFindFirstArgs = $FindFirstArgs<$Schema, "Meta">; +export type MetaExistsArgs = $ExistsArgs<$Schema, "Meta">; export type MetaCreateArgs = $CreateArgs<$Schema, "Meta">; export type MetaCreateManyArgs = $CreateManyArgs<$Schema, "Meta">; export type MetaCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Meta">; diff --git a/tests/e2e/package.json b/tests/e2e/package.json index d780cbe69..7d849e26b 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -1,6 +1,6 @@ { "name": "e2e", - "version": "3.1.1", + "version": "3.2.0", "private": true, "type": "module", "scripts": { diff --git a/tests/regression/package.json b/tests/regression/package.json index e1a60e16d..2c1bbdf54 100644 --- a/tests/regression/package.json +++ b/tests/regression/package.json @@ -1,6 +1,6 @@ { "name": "regression", - "version": "3.1.1", + "version": "3.2.0", "private": true, "type": "module", "scripts": { diff --git a/tests/regression/test/issue-204/input.ts b/tests/regression/test/issue-204/input.ts index 3c7c14554..74cd690b9 100644 --- a/tests/regression/test/issue-204/input.ts +++ b/tests/regression/test/issue-204/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 FooFindManyArgs = $FindManyArgs<$Schema, "Foo">; export type FooFindUniqueArgs = $FindUniqueArgs<$Schema, "Foo">; export type FooFindFirstArgs = $FindFirstArgs<$Schema, "Foo">; +export type FooExistsArgs = $ExistsArgs<$Schema, "Foo">; export type FooCreateArgs = $CreateArgs<$Schema, "Foo">; export type FooCreateManyArgs = $CreateManyArgs<$Schema, "Foo">; export type FooCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Foo">; diff --git a/tests/regression/test/issue-422/input.ts b/tests/regression/test/issue-422/input.ts index b2d63acdb..2efb5ebe3 100644 --- a/tests/regression/test/issue-422/input.ts +++ b/tests/regression/test/issue-422/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 SessionFindManyArgs = $FindManyArgs<$Schema, "Session">; export type SessionFindUniqueArgs = $FindUniqueArgs<$Schema, "Session">; export type SessionFindFirstArgs = $FindFirstArgs<$Schema, "Session">; +export type SessionExistsArgs = $ExistsArgs<$Schema, "Session">; export type SessionCreateArgs = $CreateArgs<$Schema, "Session">; export type SessionCreateManyArgs = $CreateManyArgs<$Schema, "Session">; export type SessionCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Session">; @@ -31,6 +32,7 @@ export type SessionGetPayload; 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">; @@ -51,6 +53,7 @@ export type UserGetPayload; 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">; diff --git a/tests/regression/test/issue-503/input.ts b/tests/regression/test/issue-503/input.ts index e1e5bb0da..2de93d48a 100644 --- a/tests/regression/test/issue-503/input.ts +++ b/tests/regression/test/issue-503/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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 InternalChatFindManyArgs = $FindManyArgs<$Schema, "InternalChat">; export type InternalChatFindUniqueArgs = $FindUniqueArgs<$Schema, "InternalChat">; export type InternalChatFindFirstArgs = $FindFirstArgs<$Schema, "InternalChat">; +export type InternalChatExistsArgs = $ExistsArgs<$Schema, "InternalChat">; export type InternalChatCreateArgs = $CreateArgs<$Schema, "InternalChat">; export type InternalChatCreateManyArgs = $CreateManyArgs<$Schema, "InternalChat">; export type InternalChatCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "InternalChat">; @@ -31,6 +32,7 @@ export type InternalChatGetPayload; export type MessageFindUniqueArgs = $FindUniqueArgs<$Schema, "Message">; export type MessageFindFirstArgs = $FindFirstArgs<$Schema, "Message">; +export type MessageExistsArgs = $ExistsArgs<$Schema, "Message">; export type MessageCreateArgs = $CreateArgs<$Schema, "Message">; export type MessageCreateManyArgs = $CreateManyArgs<$Schema, "Message">; export type MessageCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Message">; @@ -51,6 +53,7 @@ export type MessageGetPayload; export type MediaFindUniqueArgs = $FindUniqueArgs<$Schema, "Media">; export type MediaFindFirstArgs = $FindFirstArgs<$Schema, "Media">; +export type MediaExistsArgs = $ExistsArgs<$Schema, "Media">; export type MediaCreateArgs = $CreateArgs<$Schema, "Media">; export type MediaCreateManyArgs = $CreateManyArgs<$Schema, "Media">; export type MediaCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Media">; diff --git a/tests/regression/test/issue-576.test.ts b/tests/regression/test/issue-576.test.ts new file mode 100644 index 000000000..3997cf007 --- /dev/null +++ b/tests/regression/test/issue-576.test.ts @@ -0,0 +1,200 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('regression test for issue 576', async () => { + it('should support enum array fields', async () => { + const db = await createTestClient( + ` +enum Tag { + TAG1 + TAG2 + TAG3 +} + +model Foo { + id Int @id + tags Tag[] + bar Bar? +} + +model Bar { + id Int @id + fooId Int @unique + foo Foo @relation(fields: [fooId], references: [id]) +} +`, + { provider: 'postgresql', usePrismaPush: true }, + ); + + await expect( + db.foo.create({ + data: { + id: 1, + tags: ['TAG1', 'TAG2'], + }, + }), + ).resolves.toMatchObject({ id: 1, tags: ['TAG1', 'TAG2'] }); + await expect( + db.foo.update({ + where: { id: 1 }, + data: { + tags: { set: ['TAG2', 'TAG3'] }, + }, + }), + ).resolves.toMatchObject({ id: 1, tags: ['TAG2', 'TAG3'] }); + + await expect(db.foo.findFirst()).resolves.toMatchObject({ tags: ['TAG2', 'TAG3'] }); + await expect(db.foo.findFirst({ where: { tags: { equals: ['TAG2', 'TAG3'] } } })).resolves.toMatchObject({ + tags: ['TAG2', 'TAG3'], + }); + await expect(db.foo.findFirst({ where: { tags: { has: 'TAG1' } } })).toResolveNull(); + + // nested create + await expect( + db.bar.create({ + data: { id: 1, foo: { create: { id: 2, tags: ['TAG1'] } } }, + include: { foo: true }, + }), + ).resolves.toMatchObject({ foo: expect.objectContaining({ tags: ['TAG1'] }) }); + + // nested find + await expect( + db.bar.findFirst({ + where: { foo: { tags: { has: 'TAG1' } } }, + include: { foo: true }, + }), + ).resolves.toMatchObject({ foo: expect.objectContaining({ tags: ['TAG1'] }) }); + + await expect( + db.bar.findFirst({ + where: { foo: { tags: { equals: ['TAG2'] } } }, + }), + ).toResolveNull(); + }); + + it('should support enum array stored in JSON field', async () => { + const db = await createTestClient( + ` +enum Tag { + TAG1 + TAG2 + TAG3 +} + +model Foo { + id Int @id + tags Json +} +`, + { provider: 'postgresql', usePrismaPush: true }, + ); + + await expect( + db.foo.create({ + data: { + id: 1, + tags: ['TAG1', 'TAG2'], + }, + }), + ).resolves.toMatchObject({ id: 1, tags: ['TAG1', 'TAG2'] }); + await expect(db.foo.findFirst()).resolves.toMatchObject({ tags: ['TAG1', 'TAG2'] }); + }); + + it('should support enum array stored in JSON array field', async () => { + const db = await createTestClient( + ` +enum Tag { + TAG1 + TAG2 + TAG3 +} + +model Foo { + id Int @id + tags Json[] +} +`, + { provider: 'postgresql', usePrismaPush: true }, + ); + + await expect( + db.foo.create({ + data: { + id: 1, + tags: ['TAG1', 'TAG2'], + }, + }), + ).resolves.toMatchObject({ id: 1, tags: ['TAG1', 'TAG2'] }); + await expect(db.foo.findFirst()).resolves.toMatchObject({ tags: ['TAG1', 'TAG2'] }); + }); + + it('should support enum with datasource defined default pg schema', async () => { + const db = await createTestClient( + ` +datasource db { + provider = 'postgresql' + schemas = ['public', 'mySchema'] + url = '$DB_URL' + defaultSchema = 'mySchema' +} + +enum Tag { + TAG1 + TAG2 + TAG3 +} + +model Foo { + id Int @id + tags Tag[] +} +`, + { provider: 'postgresql', usePrismaPush: true }, + ); + + await expect( + db.foo.create({ + data: { + id: 1, + tags: ['TAG1', 'TAG2'], + }, + }), + ).resolves.toMatchObject({ id: 1, tags: ['TAG1', 'TAG2'] }); + await expect(db.foo.findFirst()).resolves.toMatchObject({ tags: ['TAG1', 'TAG2'] }); + }); + + it('should support enum with custom pg schema', async () => { + const db = await createTestClient( + ` +datasource db { + provider = 'postgresql' + schemas = ['public', 'mySchema'] + url = '$DB_URL' +} + +enum Tag { + TAG1 + TAG2 + TAG3 + @@schema('mySchema') +} + +model Foo { + id Int @id + tags Tag[] +} +`, + { provider: 'postgresql', usePrismaPush: true }, + ); + + await expect( + db.foo.create({ + data: { + id: 1, + tags: ['TAG1', 'TAG2'], + }, + }), + ).resolves.toMatchObject({ id: 1, tags: ['TAG1', 'TAG2'] }); + await expect(db.foo.findFirst()).resolves.toMatchObject({ tags: ['TAG1', 'TAG2'] }); + }); +}); diff --git a/tests/runtimes/bun/package.json b/tests/runtimes/bun/package.json index f9eec9375..4bf3f97cc 100644 --- a/tests/runtimes/bun/package.json +++ b/tests/runtimes/bun/package.json @@ -1,6 +1,6 @@ { "name": "bun-e2e", - "version": "3.1.1", + "version": "3.2.0", "private": true, "type": "module", "scripts": { diff --git a/tests/runtimes/bun/schemas/input.ts b/tests/runtimes/bun/schemas/input.ts index 3799802f9..c6b620ee6 100644 --- a/tests/runtimes/bun/schemas/input.ts +++ b/tests/runtimes/bun/schemas/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">; diff --git a/tests/runtimes/edge-runtime/package.json b/tests/runtimes/edge-runtime/package.json index d02c5cdab..82fd042bc 100644 --- a/tests/runtimes/edge-runtime/package.json +++ b/tests/runtimes/edge-runtime/package.json @@ -1,6 +1,6 @@ { "name": "edge-runtime-e2e", - "version": "3.1.1", + "version": "3.2.0", "private": true, "type": "module", "scripts": { diff --git a/tests/runtimes/edge-runtime/schemas/input.ts b/tests/runtimes/edge-runtime/schemas/input.ts index 3799802f9..c6b620ee6 100644 --- a/tests/runtimes/edge-runtime/schemas/input.ts +++ b/tests/runtimes/edge-runtime/schemas/input.ts @@ -6,11 +6,12 @@ /* eslint-disable */ import { type SchemaType as $Schema } from "./schema"; -import type { FindManyArgs as $FindManyArgs, FindUniqueArgs as $FindUniqueArgs, FindFirstArgs as $FindFirstArgs, 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 { 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">; @@ -31,6 +32,7 @@ export type UserGetPayload; 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">;