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 00000000..15684479 --- /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/react.ts b/packages/clients/tanstack-query/src/react.ts index 8b3d9329..75045f5c 100644 --- a/packages/clients/tanstack-query/src/react.ts +++ b/packages/clients/tanstack-query/src/react.ts @@ -55,6 +55,7 @@ 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, @@ -350,30 +351,36 @@ export function useClientQueries | undefined; if (procedures) { - const buildProcedureHooks = (endpointModel: '$procs') => { + const buildProcedureHooks = () => { return Object.keys(procedures).reduce((acc, name) => { const procDef = procedures[name]; if (procDef?.mutation) { acc[name] = { useMutation: (hookOptions?: any) => - useInternalMutation(schema, endpointModel, 'POST', name, { ...options, ...hookOptions }), + useInternalMutation(schema, CUSTOM_PROC_ROUTE_NAME, 'POST', name, { + ...options, + ...hookOptions, + }), }; } else { acc[name] = { useQuery: (args?: any, hookOptions?: any) => - useInternalQuery(schema, endpointModel, name, args, { ...options, ...hookOptions }), + useInternalQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, { + ...options, + ...hookOptions, + }), useSuspenseQuery: (args?: any, hookOptions?: any) => - useInternalSuspenseQuery(schema, endpointModel, name, args, { + useInternalSuspenseQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, { ...options, ...hookOptions, }), useInfiniteQuery: (args?: any, hookOptions?: any) => - useInternalInfiniteQuery(schema, endpointModel, name, args, { + useInternalInfiniteQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, { ...options, ...hookOptions, }), useSuspenseInfiniteQuery: (args?: any, hookOptions?: any) => - useInternalSuspenseInfiniteQuery(schema, endpointModel, name, args, { + useInternalSuspenseInfiniteQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, { ...options, ...hookOptions, }), @@ -383,7 +390,7 @@ export function useClientQueries( }; 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 0bd1bd38..0c976b71 100644 --- a/packages/clients/tanstack-query/src/svelte/index.svelte.ts +++ b/packages/clients/tanstack-query/src/svelte/index.svelte.ts @@ -56,6 +56,7 @@ 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, @@ -297,27 +298,39 @@ export function useClientQueries | undefined; if (procedures) { - const buildProcedureHooks = (endpointModel: '$procs') => { + const buildProcedureHooks = () => { return Object.keys(procedures).reduce((acc, name) => { const procDef = procedures[name]; if (procDef?.mutation) { acc[name] = { useMutation: (hookOptions?: any) => - useInternalMutation(schema, endpointModel, 'POST', name, merge(options, hookOptions)), + useInternalMutation( + schema, + CUSTOM_PROC_ROUTE_NAME, + 'POST', + name, + merge(options, hookOptions), + ), }; } else { acc[name] = { useQuery: (args?: any, hookOptions?: any) => - useInternalQuery(schema, endpointModel, name, args, merge(options, hookOptions)), + useInternalQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, merge(options, hookOptions)), useInfiniteQuery: (args?: any, hookOptions?: any) => - useInternalInfiniteQuery(schema, endpointModel, name, args, merge(options, hookOptions)), + useInternalInfiniteQuery( + schema, + CUSTOM_PROC_ROUTE_NAME, + name, + args, + merge(options, hookOptions), + ), }; } return acc; }, {} as any); }; - (result as any).$procs = buildProcedureHooks('$procs'); + (result as any).$procs = buildProcedureHooks(); } return result; @@ -533,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; + const origOnMutate = optionsValue.onMutate; + const wrappedOnMutate: typeof origOnMutate = async (...args) => { + // execute optimistic updater prior to user-provided onMutate + await optimisticUpdater(...args); - 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); + // 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 7361c2ad..bc0bf39f 100644 --- a/packages/clients/tanstack-query/src/vue.ts +++ b/packages/clients/tanstack-query/src/vue.ts @@ -54,6 +54,7 @@ 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, @@ -309,27 +310,39 @@ export function useClientQueries | undefined; if (procedures) { - const buildProcedureHooks = (endpointModel: '$procs') => { + const buildProcedureHooks = () => { return Object.keys(procedures).reduce((acc, name) => { const procDef = procedures[name]; if (procDef?.mutation) { acc[name] = { useMutation: (hookOptions?: any) => - useInternalMutation(schema, endpointModel, 'POST', name, merge(options, hookOptions)), + useInternalMutation( + schema, + CUSTOM_PROC_ROUTE_NAME, + 'POST', + name, + merge(options, hookOptions), + ), }; } else { acc[name] = { useQuery: (args?: any, hookOptions?: any) => - useInternalQuery(schema, endpointModel, name, args, merge(options, hookOptions)), + useInternalQuery(schema, CUSTOM_PROC_ROUTE_NAME, name, args, merge(options, hookOptions)), useInfiniteQuery: (args?: any, hookOptions?: any) => - useInternalInfiniteQuery(schema, endpointModel, name, args, merge(options, hookOptions)), + useInternalInfiniteQuery( + schema, + CUSTOM_PROC_ROUTE_NAME, + name, + args, + merge(options, hookOptions), + ), }; } return acc; }, {} as any); }; - (result as any).$procs = buildProcedureHooks('$procs'); + (result as any).$procs = buildProcedureHooks(); } return result; @@ -552,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); - - // 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); + // 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 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/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index fd32aa72..957b94ab 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -147,27 +147,27 @@ 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' })); + 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}[]; + result = r.rows as { exists: number | boolean }[]; } 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[0]?.exists; diff --git a/packages/server/src/api/common/procedures.ts b/packages/server/src/api/common/procedures.ts index 60680158..a0d1e5d5 100644 --- a/packages/server/src/api/common/procedures.ts +++ b/packages/server/src/api/common/procedures.ts @@ -1,7 +1,7 @@ import { ORMError } from '@zenstackhq/orm'; import type { ProcedureDef, ProcedureParam, SchemaDef } from '@zenstackhq/orm/schema'; -export const PROCEDURE_ROUTE_PREFIXES = ['$procs'] as const; +export const PROCEDURE_ROUTE_PREFIXES = '$procs' as const; export function getProcedureDef(schema: SchemaDef, proc: string): ProcedureDef | undefined { const procs = schema.procedures ?? {}; diff --git a/samples/next.js/app/api/model/[...path]/route.ts b/samples/next.js/app/api/model/[...path]/route.ts index 49ba5ff5..0c1cfd81 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 00000000..b12f4b1d --- /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 7a6da3a5..89d57397 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 4372f2a2..9dee7770 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 00000000..ace8e621 --- /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 688e7886..1ba1422a 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 b2cd96ee..72c04fe5 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 6153abe9..7308e184 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 c7d690ed..695ee053 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 e1775e12..00000000 --- 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 00000000..5baeec7a --- /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 6e02d80d..14004e7d 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 4512f245..5e39f2ae 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 00000000..6a1f2ce6 --- /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 00000000..24169b00 --- /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 00000000..068d2f96 --- /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 00000000..72304f33 --- /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 2c380de0..3df30897 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 7483280a..7e797d03 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 86aa5248..5513bc00 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 b2cd96ee..72c04fe5 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 6153abe9..7308e184 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 c7d690ed..695ee053 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 e1775e12..00000000 --- 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 00000000..5baeec7a --- /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 6e02d80d..7a4acb63 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/shared/schema.zmodel b/samples/shared/schema.zmodel new file mode 100644 index 00000000..a0d25d8a --- /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 da08e6da..520c4217 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 bbf70b8c..ab4727df 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 77a8e800..e1770a85 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 5725bf79..4f479b42 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 00000000..c84617b4 --- /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 00000000..25efb0e7 --- /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 b2cd96ee..37e85b3e 100644 --- a/samples/sveltekit/src/zenstack/input.ts +++ b/samples/sveltekit/src/zenstack/input.ts @@ -6,17 +6,49 @@ /* 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 { SimplifiedPlainResult as $Result, SelectIncludeOmit as $SelectIncludeOmit } 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">; +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 UserUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs< + $Schema, + "User" +>; export type UserUpsertArgs = $UpsertArgs<$Schema, "User">; export type UserDeleteArgs = $DeleteArgs<$Schema, "User">; export type UserDeleteManyArgs = $DeleteManyArgs<$Schema, "User">; @@ -27,16 +59,26 @@ 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>; +export type UserGetPayload< + Args extends $SelectIncludeOmit<$Schema, "User", true>, + Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>, +> = $Result<$Schema, "User", Args, Options>; export type PostFindManyArgs = $FindManyArgs<$Schema, "Post">; export type PostFindUniqueArgs = $FindUniqueArgs<$Schema, "Post">; export type PostFindFirstArgs = $FindFirstArgs<$Schema, "Post">; +export type PostExistsArgs = $ExistsArgs<$Schema, "Post">; export type PostCreateArgs = $CreateArgs<$Schema, "Post">; export type PostCreateManyArgs = $CreateManyArgs<$Schema, "Post">; -export type PostCreateManyAndReturnArgs = $CreateManyAndReturnArgs<$Schema, "Post">; +export type PostCreateManyAndReturnArgs = $CreateManyAndReturnArgs< + $Schema, + "Post" +>; export type PostUpdateArgs = $UpdateArgs<$Schema, "Post">; export type PostUpdateManyArgs = $UpdateManyArgs<$Schema, "Post">; -export type PostUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs<$Schema, "Post">; +export type PostUpdateManyAndReturnArgs = $UpdateManyAndReturnArgs< + $Schema, + "Post" +>; export type PostUpsertArgs = $UpsertArgs<$Schema, "Post">; export type PostDeleteArgs = $DeleteArgs<$Schema, "Post">; export type PostDeleteManyArgs = $DeleteManyArgs<$Schema, "Post">; @@ -47,4 +89,7 @@ export type PostWhereInput = $WhereInput<$Schema, "Post">; export type PostSelect = $SelectInput<$Schema, "Post">; export type PostInclude = $IncludeInput<$Schema, "Post">; export type PostOmit = $OmitInput<$Schema, "Post">; -export type PostGetPayload, Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>> = $Result<$Schema, "Post", Args, Options>; +export type PostGetPayload< + Args extends $SelectIncludeOmit<$Schema, "Post", true>, + Options extends $QueryOptions<$Schema> = $QueryOptions<$Schema>, +> = $Result<$Schema, "Post", Args, Options>; diff --git a/samples/sveltekit/src/zenstack/schema-lite.ts b/samples/sveltekit/src/zenstack/schema-lite.ts index 6153abe9..5b73135f 100644 --- a/samples/sveltekit/src/zenstack/schema-lite.ts +++ b/samples/sveltekit/src/zenstack/schema-lite.ts @@ -7,100 +7,118 @@ 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") - }, - createdAt: { - name: "createdAt", - type: "DateTime", - default: ExpressionUtils.call("now") - }, - updatedAt: { - name: "updatedAt", - type: "DateTime", - updatedAt: true - }, - email: { - name: "email", - type: "String", - unique: true - }, - name: { - name: "name", - type: "String", - optional: true - }, - posts: { - name: "posts", - type: "Post", - array: true, - relation: { opposite: "author" } - } - }, - idFields: ["id"], - uniqueFields: { - id: { type: "String" }, - email: { type: "String" } - } + provider = { + type: "sqlite", + } as const; + models = { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "String", + id: true, + default: ExpressionUtils.call("cuid"), }, - Post: { - name: "Post", - fields: { - id: { - name: "id", - type: "String", - id: true, - default: ExpressionUtils.call("cuid") - }, - createdAt: { - name: "createdAt", - type: "DateTime", - default: ExpressionUtils.call("now") - }, - updatedAt: { - name: "updatedAt", - type: "DateTime", - updatedAt: true - }, - title: { - name: "title", - type: "String" - }, - published: { - name: "published", - type: "Boolean", - default: false - }, - author: { - name: "author", - type: "User", - relation: { opposite: "posts", fields: ["authorId"], references: ["id"], onUpdate: "Cascade", onDelete: "Cascade" } - }, - authorId: { - name: "authorId", - type: "String", - foreignKeyFor: [ - "author" - ] - } - }, - idFields: ["id"], - uniqueFields: { - id: { type: "String" } - } - } - } as const; - authType = "User" as const; - plugins = {}; + createdAt: { + name: "createdAt", + type: "DateTime", + default: ExpressionUtils.call("now"), + }, + updatedAt: { + name: "updatedAt", + type: "DateTime", + updatedAt: true, + }, + email: { + name: "email", + type: "String", + unique: true, + }, + name: { + name: "name", + type: "String", + optional: true, + }, + posts: { + name: "posts", + type: "Post", + array: true, + relation: { opposite: "author" }, + }, + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" }, + email: { type: "String" }, + }, + }, + Post: { + name: "Post", + fields: { + id: { + name: "id", + type: "String", + id: true, + default: ExpressionUtils.call("cuid"), + }, + createdAt: { + name: "createdAt", + type: "DateTime", + default: ExpressionUtils.call("now"), + }, + updatedAt: { + name: "updatedAt", + type: "DateTime", + updatedAt: true, + }, + title: { + name: "title", + type: "String", + }, + published: { + name: "published", + type: "Boolean", + default: false, + }, + author: { + name: "author", + type: "User", + relation: { + opposite: "posts", + fields: ["authorId"], + references: ["id"], + onUpdate: "Cascade", + onDelete: "Cascade", + }, + }, + authorId: { + name: "authorId", + type: "String", + foreignKeyFor: ["author"], + }, + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" }, + }, + }, + } 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 c7d690ed..27bc05c2 100644 --- a/samples/sveltekit/src/zenstack/schema.ts +++ b/samples/sveltekit/src/zenstack/schema.ts @@ -7,109 +7,172 @@ 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, - attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], - default: ExpressionUtils.call("cuid") - }, - createdAt: { - name: "createdAt", - type: "DateTime", - attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }], - default: ExpressionUtils.call("now") - }, - updatedAt: { - name: "updatedAt", - type: "DateTime", - updatedAt: true, - attributes: [{ name: "@updatedAt" }] - }, - email: { - name: "email", - type: "String", - unique: true, - attributes: [{ name: "@unique" }] - }, - name: { - name: "name", - type: "String", - optional: true - }, - posts: { - name: "posts", - type: "Post", - array: true, - relation: { opposite: "author" } - } + provider = { + type: "sqlite", + } as const; + models = { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [ + { name: "@id" }, + { + name: "@default", + args: [{ name: "value", value: ExpressionUtils.call("cuid") }], }, - idFields: ["id"], - uniqueFields: { - id: { type: "String" }, - email: { type: "String" } - } + ], + default: ExpressionUtils.call("cuid"), }, - Post: { - name: "Post", - fields: { - id: { - name: "id", - type: "String", - id: true, - attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], - default: ExpressionUtils.call("cuid") - }, - createdAt: { - name: "createdAt", - type: "DateTime", - attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }], - default: ExpressionUtils.call("now") - }, - updatedAt: { - name: "updatedAt", - type: "DateTime", - updatedAt: true, - attributes: [{ name: "@updatedAt" }] - }, - title: { - name: "title", - type: "String" - }, - published: { - name: "published", - type: "Boolean", - attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(false) }] }], - default: false + createdAt: { + name: "createdAt", + type: "DateTime", + attributes: [ + { + name: "@default", + args: [{ name: "value", value: ExpressionUtils.call("now") }], + }, + ], + default: ExpressionUtils.call("now"), + }, + updatedAt: { + name: "updatedAt", + type: "DateTime", + updatedAt: true, + attributes: [{ name: "@updatedAt" }], + }, + email: { + name: "email", + type: "String", + unique: true, + attributes: [{ name: "@unique" }], + }, + name: { + name: "name", + type: "String", + optional: true, + }, + posts: { + name: "posts", + type: "Post", + array: true, + relation: { opposite: "author" }, + }, + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" }, + email: { type: "String" }, + }, + }, + Post: { + name: "Post", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [ + { name: "@id" }, + { + name: "@default", + args: [{ name: "value", value: ExpressionUtils.call("cuid") }], + }, + ], + default: ExpressionUtils.call("cuid"), + }, + createdAt: { + name: "createdAt", + type: "DateTime", + attributes: [ + { + name: "@default", + args: [{ name: "value", value: ExpressionUtils.call("now") }], + }, + ], + default: ExpressionUtils.call("now"), + }, + updatedAt: { + name: "updatedAt", + type: "DateTime", + updatedAt: true, + attributes: [{ name: "@updatedAt" }], + }, + title: { + name: "title", + type: "String", + }, + published: { + name: "published", + type: "Boolean", + attributes: [ + { + name: "@default", + args: [{ name: "value", value: ExpressionUtils.literal(false) }], + }, + ], + default: false, + }, + author: { + name: "author", + type: "User", + attributes: [ + { + name: "@relation", + args: [ + { + name: "fields", + value: ExpressionUtils.array([ + ExpressionUtils.field("authorId"), + ]), }, - author: { - name: "author", - type: "User", - attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array([ExpressionUtils.field("authorId")]) }, { name: "references", value: ExpressionUtils.array([ExpressionUtils.field("id")]) }, { name: "onUpdate", value: ExpressionUtils.literal("Cascade") }, { name: "onDelete", value: ExpressionUtils.literal("Cascade") }] }], - relation: { opposite: "posts", fields: ["authorId"], references: ["id"], onUpdate: "Cascade", onDelete: "Cascade" } + { + name: "references", + value: ExpressionUtils.array([ExpressionUtils.field("id")]), }, - authorId: { - name: "authorId", - type: "String", - foreignKeyFor: [ - "author" - ] - } + { name: "onUpdate", value: ExpressionUtils.literal("Cascade") }, + { name: "onDelete", value: ExpressionUtils.literal("Cascade") }, + ], }, - idFields: ["id"], - uniqueFields: { - id: { type: "String" } - } - } - } as const; - authType = "User" as const; - plugins = {}; + ], + relation: { + opposite: "posts", + fields: ["authorId"], + references: ["id"], + onUpdate: "Cascade", + onDelete: "Cascade", + }, + }, + authorId: { + name: "authorId", + type: "String", + foreignKeyFor: ["author"], + }, + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" }, + }, + }, + } 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 e1775e12..00000000 --- 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 00000000..67e69c0f --- /dev/null +++ b/samples/sveltekit/src/zenstack/schema.zmodel @@ -0,0 +1 @@ +../../../shared/schema.zmodel \ No newline at end of file