├── src
├── app.css
├── lib
│ ├── constant.ts
│ ├── prisma.ts
│ ├── auth.ts
│ ├── components
│ │ ├── Avatar.svelte
│ │ ├── TimeInfo.svelte
│ │ ├── Spaces.svelte
│ │ ├── BreadCrumb.svelte
│ │ ├── SpaceMembers.svelte
│ │ ├── NavBar.svelte
│ │ ├── Todo.svelte
│ │ ├── TodoList.svelte
│ │ ├── CreateListDialog.svelte
│ │ └── ManageMembers.svelte
│ └── hooks
│ │ ├── index.ts
│ │ ├── __model_meta.ts
│ │ ├── todo.ts
│ │ ├── user.ts
│ │ ├── list.ts
│ │ ├── space.ts
│ │ └── space-user.ts
├── routes
│ ├── (app)
│ │ ├── +page.server.ts
│ │ ├── +layout.server.ts
│ │ ├── +layout.svelte
│ │ ├── space
│ │ │ └── [slug]
│ │ │ │ ├── +layout.server.ts
│ │ │ │ ├── +page.server.ts
│ │ │ │ ├── +page.svelte
│ │ │ │ └── [listId]
│ │ │ │ ├── +page.svelte
│ │ │ │ └── +page.server.ts
│ │ ├── +page.svelte
│ │ └── create-space
│ │ │ ├── +page.server.ts
│ │ │ └── +page.svelte
│ ├── api
│ │ └── auth
│ │ │ ├── signout
│ │ │ └── +server.ts
│ │ │ └── signin
│ │ │ └── +server.ts
│ ├── +layout.svelte
│ └── (auth)
│ │ ├── signin
│ │ ├── +page.server.ts
│ │ └── +page.svelte
│ │ └── signup
│ │ ├── +page.server.ts
│ │ └── +page.svelte
├── app.html
├── app.d.ts
├── components
│ └── List.svelte
└── hooks.server.ts
├── static
├── logo.png
├── avatar.jpg
└── auth-bg.jpg
├── postcss.config.js
├── .gitignore
├── prisma
├── migrations
│ ├── migration_lock.toml
│ ├── 20241222160427_add_space_owner
│ │ └── migration.sql
│ └── 20230905040838_init
│ │ └── migration.sql
└── schema.prisma
├── .env
├── tailwind.config.js
├── tsconfig.json
├── svelte.config.js
├── .github
└── workflows
│ ├── build.yml
│ └── update.yml
├── vite.config.js
├── README.md
├── package.json
└── schema.zmodel
/src/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/src/lib/constant.ts:
--------------------------------------------------------------------------------
1 | export const JWT_TOKEN_COOKIE_NAME = 'ZenStack-Todo-Token';
2 |
--------------------------------------------------------------------------------
/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenstackhq/sample-todo-sveltekit/HEAD/static/logo.png
--------------------------------------------------------------------------------
/static/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenstackhq/sample-todo-sveltekit/HEAD/static/avatar.jpg
--------------------------------------------------------------------------------
/static/auth-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenstackhq/sample-todo-sveltekit/HEAD/static/auth-bg.jpg
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | *.db
10 |
--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (e.g., Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | DATABASE_URL=postgres://postgres:abc123@localhost:5432/todo-sveltekit
2 | DATABASE_DIRECT_URL=postgres://postgres:abc123@localhost:5432/todo-sveltekit
3 | JWT_SECRET=secret
--------------------------------------------------------------------------------
/src/routes/(app)/+page.server.ts:
--------------------------------------------------------------------------------
1 | import type { PageServerLoad } from './$types';
2 |
3 | export const load = (async ({ locals }) => {
4 | const spaces = await locals.db.space.findMany({
5 | orderBy: { createdAt: 'desc' },
6 | });
7 | return { user: locals.user!, spaces };
8 | }) satisfies PageServerLoad;
9 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./src/**/*.{html,js,svelte,ts}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [require('daisyui'), require('@tailwindcss/line-clamp')],
8 | daisyui: {
9 | themes: false,
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/src/routes/api/auth/signout/+server.ts:
--------------------------------------------------------------------------------
1 | import { JWT_TOKEN_COOKIE_NAME } from '$lib/constant';
2 | import { redirect, type RequestHandler } from '@sveltejs/kit';
3 |
4 | export const POST = (({ cookies }) => {
5 | cookies.delete(JWT_TOKEN_COOKIE_NAME, { path: '/' });
6 | throw redirect(303, '/signin');
7 | }) satisfies RequestHandler;
8 |
--------------------------------------------------------------------------------
/src/lib/prisma.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client';
2 | import { enhance } from '@zenstackhq/runtime';
3 |
4 | const prisma = new PrismaClient();
5 |
6 | export function getEnhancedPrisma(userId?: string) {
7 | return enhance(prisma, { user: userId ? { id: userId } : undefined });
8 | }
9 |
10 | export default prisma;
11 |
--------------------------------------------------------------------------------
/src/routes/(app)/+layout.server.ts:
--------------------------------------------------------------------------------
1 | import { redirect } from '@sveltejs/kit';
2 | import type { LayoutServerLoad } from './$types';
3 |
4 | export const load = (async ({ locals }) => {
5 | if (!locals.user) {
6 | throw redirect(303, '/signin');
7 | }
8 | return {
9 | user: locals.user,
10 | };
11 | }) satisfies LayoutServerLoad;
12 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 |
4 | import type { PrismaClient, User } from '@prisma/client';
5 |
6 | declare global {
7 | namespace App {
8 | interface Locals {
9 | user?: User;
10 | db: PrismaClient;
11 | }
12 | }
13 | }
14 |
15 | export {};
16 |
--------------------------------------------------------------------------------
/src/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { env } from '$env/dynamic/private';
2 | import type { User } from '@prisma/client';
3 | import jwt from 'jsonwebtoken';
4 |
5 | export function createToken(user: User) {
6 | return jwt.sign(
7 | {
8 | sub: user.id,
9 | email: user.email,
10 | },
11 | env.JWT_SECRET,
12 | { expiresIn: '7d' }
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | },
13 | "ts-node": {
14 | "esm": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/routes/(app)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/routes/(app)/space/[slug]/+layout.server.ts:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 | import type { LayoutServerLoad } from './$types';
3 |
4 | export const load = (async ({ locals, params }) => {
5 | const space = await locals.db.space.findUnique({
6 | where: { slug: params.slug },
7 | });
8 | if (!space) {
9 | throw error(404, 'Space not found');
10 | }
11 | return { space };
12 | }) satisfies LayoutServerLoad;
13 |
--------------------------------------------------------------------------------
/prisma/migrations/20241222160427_add_space_owner/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - Added the required column `ownerId` to the `Space` table without a default value. This is not possible if the table is not empty.
5 |
6 | */
7 | -- AlterTable
8 | ALTER TABLE "Space" ADD COLUMN "ownerId" TEXT NOT NULL;
9 |
10 | -- AddForeignKey
11 | ALTER TABLE "Space" ADD CONSTRAINT "Space_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
12 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | // import preprocess from 'svelte-preprocess';
3 | import { vitePreprocess } from '@sveltejs/kit/vite';
4 |
5 | /** @type {import('@sveltejs/kit').Config} */
6 | const config = {
7 | // Consult https://github.com/sveltejs/svelte-preprocess
8 | // for more information about preprocessors
9 | preprocess: vitePreprocess(),
10 |
11 | kit: {
12 | adapter: adapter(),
13 | },
14 | };
15 |
16 | export default config;
17 |
--------------------------------------------------------------------------------
/src/lib/components/Avatar.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/src/lib/components/TimeInfo.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | {value.completedAt
13 | ? `Completed ${moment(value.completedAt).fromNow()}`
14 | : value.createdAt === value.updatedAt
15 | ? `Created ${moment(value.createdAt).fromNow()}`
16 | : `Updated ${moment(value.updatedAt).fromNow()}`}
17 |
18 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | env:
4 | DO_NOT_TRACK: '1'
5 |
6 | on:
7 | push:
8 | branches: ['main']
9 | pull_request:
10 | branches: ['main']
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Use Node.js 20.x
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: 20.x
22 | cache: 'npm'
23 | - run: npm ci
24 | - run: npm run build
25 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 |
3 | /** @type {import('vite').UserConfig} */
4 | const config = {
5 | plugins: [sveltekit()],
6 | resolve: {
7 | alias: {
8 | '.prisma/client/index-browser':
9 | './node_modules/.prisma/client/index-browser.js',
10 | },
11 | },
12 | ssr: {
13 | // this is needed to make sure "vite" processes "@tanstack/svelte-query" imported inside
14 | // "@zenstackhq/tanstack-query"
15 | noExternal: ['@zenstackhq/tanstack-query'],
16 | },
17 | };
18 |
19 | export default config;
20 |
--------------------------------------------------------------------------------
/src/lib/hooks/index.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 |
7 | export * from './space';
8 | export * from './space-user';
9 | export * from './user';
10 | export * from './list';
11 | export * from './todo';
12 | export { getQueryKey } from '@zenstackhq/tanstack-query/runtime-v5';
13 | export { SvelteQueryContextKey, setHooksContext } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
14 | export { default as metadata } from './__model_meta';
15 |
--------------------------------------------------------------------------------
/src/lib/components/Spaces.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
21 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/routes/(app)/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 | Welcome {data.user.name || data.user.email}!
11 |
12 |
13 |
23 |
24 |
--------------------------------------------------------------------------------
/src/routes/(app)/space/[slug]/+page.server.ts:
--------------------------------------------------------------------------------
1 | import type { PageServerLoad } from './$types';
2 |
3 | export const load = (async ({ locals, params }) => {
4 | const lists = await locals.db.list.findMany({
5 | where: {
6 | space: { slug: params.slug },
7 | },
8 | include: {
9 | owner: true,
10 | },
11 | orderBy: {
12 | updatedAt: 'desc',
13 | },
14 | });
15 |
16 | const members = locals.db.spaceUser.findMany({
17 | where: {
18 | space: { slug: params.slug },
19 | },
20 | include: {
21 | user: true,
22 | },
23 | orderBy: {
24 | role: 'desc',
25 | },
26 | });
27 |
28 | return {
29 | lists,
30 | members,
31 | };
32 | }) satisfies PageServerLoad;
33 |
--------------------------------------------------------------------------------
/.github/workflows/update.yml:
--------------------------------------------------------------------------------
1 | name: Update ZenStack
2 |
3 | env:
4 | DO_NOT_TRACK: '1'
5 |
6 | on:
7 | workflow_dispatch:
8 | repository_dispatch:
9 | types: [zenstack-release]
10 |
11 | jobs:
12 | update:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Update to latest ZenStack
18 | run: |
19 | git config --global user.name ymc9
20 | git config --global user.email yiming@whimslab.io
21 | npm ci
22 | npm run up
23 |
24 | - name: Build
25 | run: |
26 | npm run build
27 |
28 | - name: Commit and push
29 | run: |
30 | git add .
31 | git commit -m "chore: update to latest ZenStack" || true
32 | git push || true
33 |
--------------------------------------------------------------------------------
/src/routes/api/auth/signin/+server.ts:
--------------------------------------------------------------------------------
1 | import { createToken } from '$lib/auth';
2 | import { JWT_TOKEN_COOKIE_NAME } from '$lib/constant';
3 | import prisma from '$lib/prisma';
4 | import { error, json, type RequestHandler } from '@sveltejs/kit';
5 | import bcrypt from 'bcryptjs';
6 |
7 | export const POST = (async ({ request, cookies }) => {
8 | const { email, password } = await request.json();
9 |
10 | if (typeof email !== 'string' || typeof password !== 'string') {
11 | throw error(400, 'Invalid credentials');
12 | }
13 |
14 | const user = await prisma.user.findFirst({
15 | where: { email },
16 | });
17 | if (!user || !bcrypt.compareSync(password, user.password)) {
18 | throw error(401, 'Invalid credentials');
19 | }
20 |
21 | const token = createToken(user);
22 | cookies.set(JWT_TOKEN_COOKIE_NAME, token, {
23 | httpOnly: true,
24 | expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
25 | path: '/',
26 | });
27 | return json({ success: true, data: user });
28 | }) satisfies RequestHandler;
29 |
--------------------------------------------------------------------------------
/src/components/List.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 | {#if $posts.data}
28 | {#each $posts.data.pages as page}
29 | {#each page as post (post.id)}
30 |
{post.title} by {post.author.email}
31 | {/each}
32 | {/each}
33 | {/if}
34 | {#if $posts.hasNextPage}
35 | $posts.fetchNextPage()}> Load more
36 | {/if}
37 |
38 |
--------------------------------------------------------------------------------
/src/lib/components/BreadCrumb.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 | {#if items.length > 0}
28 | {#each items as item (item.link)}
29 |
30 |
31 | {item.text}
32 |
33 |
34 | {/each}
35 | {/if}
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/routes/(auth)/signin/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { createToken } from '$lib/auth';
2 | import { JWT_TOKEN_COOKIE_NAME } from '$lib/constant';
3 | import prisma from '$lib/prisma';
4 | import { fail, redirect, type Actions } from '@sveltejs/kit';
5 | import bcrypt from 'bcryptjs';
6 |
7 | export const actions = {
8 | default: async ({ request, cookies }) => {
9 | const data = await request.formData();
10 |
11 | const email = data.get('email');
12 | const password = data.get('password');
13 |
14 | if (typeof email !== 'string' || typeof password !== 'string') {
15 | return fail(400, { email, password, missing: true });
16 | }
17 |
18 | const user = await prisma.user.findFirst({
19 | where: { email },
20 | });
21 | if (!user || !bcrypt.compareSync(password, user.password)) {
22 | return fail(401, { email, password, invalid: true });
23 | }
24 |
25 | const token = createToken(user);
26 | cookies.set(JWT_TOKEN_COOKIE_NAME, token, {
27 | httpOnly: true,
28 | expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
29 | path: '/',
30 | });
31 | throw redirect(303, `/`);
32 | },
33 | } satisfies Actions;
34 |
--------------------------------------------------------------------------------
/src/routes/(app)/space/[slug]/+page.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
23 | Create a list
24 |
25 |
26 |
27 |
28 |
29 | {#each data.lists as list (list.id)}
30 |
31 |
32 |
33 | {/each}
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/routes/(app)/space/[slug]/[listId]/+page.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {data.list?.title}
18 |
19 |
33 |
34 | {#each data.todos as todo (todo.id)}
35 |
36 | {/each}
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { env } from '$env/dynamic/private';
2 | import { JWT_TOKEN_COOKIE_NAME } from '$lib/constant';
3 | import prisma, { getEnhancedPrisma } from '$lib/prisma';
4 | import type { Handle } from '@sveltejs/kit';
5 | import { sequence } from '@sveltejs/kit/hooks';
6 | import zenstack from '@zenstackhq/server/sveltekit';
7 | import jwt from 'jsonwebtoken';
8 |
9 | const auth = (async ({ event, resolve }) => {
10 | const token = event.cookies.get(JWT_TOKEN_COOKIE_NAME);
11 | if (token) {
12 | try {
13 | const decoded = jwt.verify(token, env.JWT_SECRET);
14 | const user = await prisma.user.findUnique({
15 | where: { id: decoded.sub as string },
16 | });
17 | if (user) {
18 | event.locals.user = user;
19 | } else {
20 | console.warn('User not found:', decoded.sub);
21 | event.cookies.delete(JWT_TOKEN_COOKIE_NAME, { path: '/' });
22 | }
23 | } catch {
24 | event.cookies.delete(JWT_TOKEN_COOKIE_NAME, { path: '/' });
25 | }
26 | }
27 |
28 | event.locals.db = getEnhancedPrisma(
29 | event.locals.user ? event.locals.user.id : undefined
30 | );
31 |
32 | return resolve(event);
33 | }) satisfies Handle;
34 |
35 | const crud = zenstack.SvelteKitHandler({
36 | prefix: '/api/model',
37 | getPrisma: (event) => event.locals.db,
38 | });
39 |
40 | export const handle = sequence(auth, crud);
41 |
--------------------------------------------------------------------------------
/src/lib/components/SpaceMembers.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {#each members as member (member.id)}
20 |
21 | {/each}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Manage Members of {space.name}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Close
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/routes/(app)/create-space/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { SpaceUserRole, type Space } from '@prisma/client';
2 | import { error, fail, redirect, type Actions } from '@sveltejs/kit';
3 | import { isPrismaClientKnownRequestError } from '@zenstackhq/runtime';
4 |
5 | export const actions = {
6 | default: async ({ request, locals }) => {
7 | if (!locals.user) {
8 | throw error(401, 'Unauthorized');
9 | }
10 |
11 | const data = await request.formData();
12 | const name = data.get('name');
13 | const slug = data.get('slug');
14 |
15 | if (typeof name !== 'string' || typeof slug !== 'string') {
16 | return fail(400, { name, slug, missing: true });
17 | }
18 |
19 | let space: Space;
20 | try {
21 | space = await locals.db.space.create({
22 | data: {
23 | name,
24 | slug,
25 | owner: { connect: { id: locals.user.id } },
26 | members: {
27 | create: {
28 | user: { connect: { id: locals.user.id } },
29 | role: SpaceUserRole.ADMIN,
30 | },
31 | },
32 | },
33 | });
34 | } catch (err) {
35 | if (isPrismaClientKnownRequestError(err) && err.code === 'P2002') {
36 | return fail(400, { name, slug, dup: true });
37 | } else {
38 | throw err;
39 | }
40 | }
41 |
42 | throw redirect(303, `/space/${space.slug}`);
43 | },
44 | } satisfies Actions;
45 |
--------------------------------------------------------------------------------
/src/routes/(app)/space/[slug]/[listId]/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit';
2 | import type { Actions, PageServerLoad } from './$types';
3 |
4 | export const load = (async ({ locals, params }) => {
5 | const list = await locals.db.list.findUnique({
6 | where: { id: params?.listId as string },
7 | });
8 | if (!list) {
9 | throw error(404, 'List not found');
10 | }
11 |
12 | const todos = await locals.db.todo.findMany({
13 | where: { listId: params?.listId as string },
14 | include: {
15 | owner: true,
16 | },
17 | orderBy: {
18 | updatedAt: 'desc',
19 | },
20 | });
21 | return { list, todos };
22 | }) satisfies PageServerLoad;
23 |
24 | export const actions = {
25 | create: async ({ request, locals, params }) => {
26 | const data = await request.formData();
27 | const title = data.get('title') as string;
28 | await locals.db.todo.create({
29 | data: {
30 | title,
31 | list: { connect: { id: params.listId } },
32 | },
33 | });
34 | },
35 |
36 | complete: async ({ request, locals }) => {
37 | const data = await request.formData();
38 | const todoId = data.get('todoId') as string;
39 | const completed = data.get('completed') as string;
40 | await locals.db.todo.update({
41 | where: { id: todoId },
42 | data: { completedAt: completed === 'on' ? new Date() : null },
43 | });
44 | },
45 |
46 | delete: async ({ request, locals }) => {
47 | const data = await request.formData();
48 | const todoId = data.get('todoId') as string;
49 | await locals.db.todo.delete({ where: { id: todoId } });
50 | },
51 | } satisfies Actions;
52 |
--------------------------------------------------------------------------------
/src/lib/components/NavBar.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 | {#if user}{user.name || user.email}
{/if}
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/routes/(app)/create-space/+page.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
11 |
12 | # ZenStack Todo Sample With SvelteKit
13 |
14 | This project is a collaborative Todo app built with [SvelteKit](https://kit.svelte.dev/) and [ZenStack](https://zenstack.dev).
15 |
16 | In this fictitious app, users can be invited to workspaces where they can collaborate on todos. Public todo lists are visible to all members in the workspace.
17 |
18 | ## Live deployment
19 | https://sample-todo-sveltekit.vercel.app/
20 |
21 | ## Features
22 |
23 | - User signup/signin
24 | - Creating workspaces and inviting members
25 | - Data segregation and permission control
26 |
27 | ## Running the sample
28 |
29 | 1. Setup a new database
30 |
31 | It use PostgreSQL by default, if you want to use MySQL, simply change the db datasource provider to `mysql` in `schema.zmodel` file.
32 |
33 | You can launch a PostgreSQL instance locally, or create one from a hoster like [Supabase](https://supabase.com). Create a new database for this app, and set the connection string in .env file.
34 |
35 | 1. Install dependencies
36 |
37 | ```bash
38 | npm install
39 | ```
40 |
41 | 1. Generate server and client-side code from model
42 |
43 | ```bash
44 | npm run generate
45 | ```
46 |
47 | 1. Synchronize database schema
48 |
49 | ```bash
50 | npm run db:push
51 | ```
52 |
53 | 1. Start dev server
54 |
55 | ```bash
56 | npm run dev
57 | ```
58 |
59 |
60 |
61 | ## Feedback and Issues
62 | If you encounter any issue or have any feedback, please create a new issue in our main repository so we could respond to it promptly:
63 |
64 | [https://github.com/zenstackhq/zenstack](https://github.com/zenstackhq/zenstack)
65 |
--------------------------------------------------------------------------------
/src/lib/components/Todo.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
17 |
18 |
25 | {value.title}
26 |
27 |
28 |
43 |
57 |
58 |
59 |
63 |
64 |
--------------------------------------------------------------------------------
/src/routes/(auth)/signup/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { createToken } from '$lib/auth';
2 | import { JWT_TOKEN_COOKIE_NAME } from '$lib/constant';
3 | import { getEnhancedPrisma } from '$lib/prisma';
4 | import { SpaceUserRole } from '@prisma/client';
5 | import { fail, redirect, type Actions } from '@sveltejs/kit';
6 | import { isPrismaClientKnownRequestError } from '@zenstackhq/runtime';
7 | import { nanoid } from 'nanoid';
8 |
9 | export const actions = {
10 | default: async ({ request, cookies, locals }) => {
11 | const data = await request.formData();
12 |
13 | const email = data.get('email');
14 | const password = data.get('password');
15 |
16 | if (typeof email !== 'string' || typeof password !== 'string') {
17 | return fail(400, { email, password, missing: true });
18 | }
19 |
20 | let db = locals.db;
21 |
22 | try {
23 | // create the user together with a default space
24 | const user = await db.user.create({
25 | data: {
26 | email,
27 | password,
28 | },
29 | });
30 |
31 | // use db under the context of the new user
32 | db = getEnhancedPrisma(user.id);
33 |
34 | const space = await db.space.create({
35 | data: {
36 | name: `My Space`,
37 | slug: nanoid(8),
38 | owner: { connect: { id: user.id } },
39 | members: {
40 | create: {
41 | user: { connect: { id: user.id } },
42 | role: SpaceUserRole.ADMIN,
43 | },
44 | },
45 | },
46 | });
47 | console.log('Space created for user:', space);
48 |
49 | const token = createToken(user);
50 | cookies.set(JWT_TOKEN_COOKIE_NAME, token, {
51 | httpOnly: true,
52 | expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
53 | path: '/',
54 | });
55 | } catch (err) {
56 | if (isPrismaClientKnownRequestError(err) && err.code === 'P2002') {
57 | return fail(400, { email, password, dup: true });
58 | } else {
59 | throw err;
60 | }
61 | }
62 |
63 | throw redirect(303, `/`);
64 | },
65 | } satisfies Actions;
66 |
--------------------------------------------------------------------------------
/src/lib/components/TodoList.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 |
73 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////////////////////////////////
2 | // DO NOT MODIFY THIS FILE //
3 | // This file is automatically generated by ZenStack CLI and should not be manually updated. //
4 | //////////////////////////////////////////////////////////////////////////////////////////////
5 |
6 | datasource db {
7 | provider = "postgresql"
8 | url = env("DATABASE_URL")
9 | directUrl = env("DATABASE_DIRECT_URL")
10 | }
11 |
12 | generator client {
13 | provider = "prisma-client-js"
14 | }
15 |
16 | enum SpaceUserRole {
17 | USER
18 | ADMIN
19 | }
20 |
21 | model Space {
22 | id String @id() @default(uuid())
23 | createdAt DateTime @default(now())
24 | updatedAt DateTime @updatedAt()
25 | owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
26 | ownerId String
27 | name String
28 | slug String @unique()
29 | members SpaceUser[]
30 | lists List[]
31 | }
32 |
33 | model SpaceUser {
34 | id String @id() @default(uuid())
35 | createdAt DateTime @default(now())
36 | updatedAt DateTime @updatedAt()
37 | space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
38 | spaceId String
39 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
40 | userId String
41 | role SpaceUserRole
42 |
43 | @@unique([userId, spaceId])
44 | }
45 |
46 | model User {
47 | id String @id() @default(cuid())
48 | email String @unique()
49 | password String
50 | name String?
51 | ownedSpaces Space[]
52 | memberships SpaceUser[]
53 | todos Todo[]
54 | lists List[]
55 | }
56 |
57 | model List {
58 | id String @id() @default(uuid())
59 | createdAt DateTime @default(now())
60 | updatedAt DateTime @updatedAt()
61 | space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
62 | spaceId String
63 | owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
64 | ownerId String
65 | title String
66 | private Boolean @default(false)
67 | todos Todo[]
68 | }
69 |
70 | model Todo {
71 | id String @id() @default(uuid())
72 | createdAt DateTime @default(now())
73 | updatedAt DateTime @updatedAt()
74 | owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
75 | ownerId String
76 | list List @relation(fields: [listId], references: [id], onDelete: Cascade)
77 | listId String
78 | title String
79 | completedAt DateTime?
80 | }
81 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rest-sveltekit",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "generate": "zenstack generate",
7 | "dev": "vite dev",
8 | "build": "npm run generate && vite build",
9 | "vercel-build": "npm run build && npm run db:deploy",
10 | "preview": "vite preview",
11 | "db:push": "prisma db push",
12 | "db:migrate": "prisma migrate dev",
13 | "db:deploy": "prisma migrate deploy",
14 | "db:reset": "prisma migrate reset",
15 | "db:browse": "prisma studio",
16 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
17 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
18 | "package-clean": "npm rm zenstack @zenstackhq/runtime @zenstackhq/server @zenstackhq/tanstack-query",
19 | "up": "npm run package-clean && npm install -D zenstack@latest && npm install @zenstackhq/runtime@latest @zenstackhq/server@latest @zenstackhq/tanstack-query@latest",
20 | "up-preview": "npm run package-clean && npm install --registry https://preview.registry.zenstack.dev -D zenstack@latest && npm install --registry https://preview.registry.zenstack.dev @zenstackhq/runtime@latest @zenstackhq/server@latest @zenstackhq/tanstack-query@latest"
21 | },
22 | "devDependencies": {
23 | "@sveltejs/adapter-auto": "^2.1.0",
24 | "@sveltejs/kit": "^1.22.4",
25 | "@sveltejs/package": "^2.2.0",
26 | "@tailwindcss/line-clamp": "^0.4.4",
27 | "@types/bcryptjs": "^2.4.2",
28 | "@types/jsonwebtoken": "^9.0.1",
29 | "@typescript-eslint/eslint-plugin": "^5.45.0",
30 | "@typescript-eslint/parser": "^5.45.0",
31 | "autoprefixer": "^10.4.14",
32 | "eslint": "^8.28.0",
33 | "eslint-config-prettier": "^8.5.0",
34 | "postcss": "^8.4.23",
35 | "prettier": "^2.8.0",
36 | "prettier-plugin-svelte": "^2.8.1",
37 | "prisma": "^6.5.0",
38 | "svelte": "^4.1.2",
39 | "svelte-check": "^3.4.6",
40 | "tailwindcss": "^3.3.2",
41 | "ts-node": "10.9.1",
42 | "tslib": "^2.4.1",
43 | "typescript": "^5.8.2",
44 | "vite": "^4.4.4",
45 | "zenstack": "^2.22.1"
46 | },
47 | "type": "module",
48 | "dependencies": {
49 | "@prisma/client": "^6.5.0",
50 | "@steeze-ui/heroicons": "^2.2.3",
51 | "@steeze-ui/svelte-icon": "^1.5.0",
52 | "@tanstack/svelte-query": "^4.32.6",
53 | "@zenstackhq/runtime": "^2.22.1",
54 | "@zenstackhq/server": "^2.22.1",
55 | "@zenstackhq/tanstack-query": "^2.22.1",
56 | "bcryptjs": "^2.4.3",
57 | "daisyui": "^2.51.5",
58 | "jsonwebtoken": "^9.0.0",
59 | "moment": "^2.29.4",
60 | "nanoid": "^4.0.2",
61 | "superjson": "^1.12.3",
62 | "zod": "^3.25.0",
63 | "zod-validation-error": "^1.3.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/prisma/migrations/20230905040838_init/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateEnum
2 | CREATE TYPE "SpaceUserRole" AS ENUM ('USER', 'ADMIN');
3 |
4 | -- CreateTable
5 | CREATE TABLE "Space" (
6 | "id" TEXT NOT NULL,
7 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
8 | "updatedAt" TIMESTAMP(3) NOT NULL,
9 | "name" TEXT NOT NULL,
10 | "slug" TEXT NOT NULL,
11 |
12 | CONSTRAINT "Space_pkey" PRIMARY KEY ("id")
13 | );
14 |
15 | -- CreateTable
16 | CREATE TABLE "SpaceUser" (
17 | "id" TEXT NOT NULL,
18 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
19 | "updatedAt" TIMESTAMP(3) NOT NULL,
20 | "spaceId" TEXT NOT NULL,
21 | "userId" TEXT NOT NULL,
22 | "role" "SpaceUserRole" NOT NULL,
23 |
24 | CONSTRAINT "SpaceUser_pkey" PRIMARY KEY ("id")
25 | );
26 |
27 | -- CreateTable
28 | CREATE TABLE "User" (
29 | "id" TEXT NOT NULL,
30 | "email" TEXT NOT NULL,
31 | "password" TEXT NOT NULL,
32 | "name" TEXT,
33 |
34 | CONSTRAINT "User_pkey" PRIMARY KEY ("id")
35 | );
36 |
37 | -- CreateTable
38 | CREATE TABLE "List" (
39 | "id" TEXT NOT NULL,
40 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
41 | "updatedAt" TIMESTAMP(3) NOT NULL,
42 | "spaceId" TEXT NOT NULL,
43 | "ownerId" TEXT NOT NULL,
44 | "title" TEXT NOT NULL,
45 | "private" BOOLEAN NOT NULL DEFAULT false,
46 |
47 | CONSTRAINT "List_pkey" PRIMARY KEY ("id")
48 | );
49 |
50 | -- CreateTable
51 | CREATE TABLE "Todo" (
52 | "id" TEXT NOT NULL,
53 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
54 | "updatedAt" TIMESTAMP(3) NOT NULL,
55 | "ownerId" TEXT NOT NULL,
56 | "listId" TEXT NOT NULL,
57 | "title" TEXT NOT NULL,
58 | "completedAt" TIMESTAMP(3),
59 |
60 | CONSTRAINT "Todo_pkey" PRIMARY KEY ("id")
61 | );
62 |
63 | -- CreateIndex
64 | CREATE UNIQUE INDEX "Space_slug_key" ON "Space"("slug");
65 |
66 | -- CreateIndex
67 | CREATE UNIQUE INDEX "SpaceUser_userId_spaceId_key" ON "SpaceUser"("userId", "spaceId");
68 |
69 | -- CreateIndex
70 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
71 |
72 | -- AddForeignKey
73 | ALTER TABLE "SpaceUser" ADD CONSTRAINT "SpaceUser_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space"("id") ON DELETE CASCADE ON UPDATE CASCADE;
74 |
75 | -- AddForeignKey
76 | ALTER TABLE "SpaceUser" ADD CONSTRAINT "SpaceUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
77 |
78 | -- AddForeignKey
79 | ALTER TABLE "List" ADD CONSTRAINT "List_spaceId_fkey" FOREIGN KEY ("spaceId") REFERENCES "Space"("id") ON DELETE CASCADE ON UPDATE CASCADE;
80 |
81 | -- AddForeignKey
82 | ALTER TABLE "List" ADD CONSTRAINT "List_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
83 |
84 | -- AddForeignKey
85 | ALTER TABLE "Todo" ADD CONSTRAINT "Todo_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
86 |
87 | -- AddForeignKey
88 | ALTER TABLE "Todo" ADD CONSTRAINT "Todo_listId_fkey" FOREIGN KEY ("listId") REFERENCES "List"("id") ON DELETE CASCADE ON UPDATE CASCADE;
89 |
--------------------------------------------------------------------------------
/src/lib/components/CreateListDialog.svelte:
--------------------------------------------------------------------------------
1 |
41 |
42 |
43 |
49 |
50 |
51 |
Create a Todo list
52 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/src/lib/components/ManageMembers.svelte:
--------------------------------------------------------------------------------
1 |
67 |
68 |
118 |
--------------------------------------------------------------------------------
/src/routes/(auth)/signin/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
102 |
--------------------------------------------------------------------------------
/src/routes/(auth)/signup/+page.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
106 |
--------------------------------------------------------------------------------
/schema.zmodel:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = 'postgresql'
7 | url = env('DATABASE_URL')
8 | directUrl = env('DATABASE_DIRECT_URL')
9 | }
10 |
11 | /*
12 | * Enum for user's role in a space
13 | */
14 | enum SpaceUserRole {
15 | USER
16 | ADMIN
17 | }
18 |
19 | plugin hooks {
20 | provider = '@zenstackhq/tanstack-query'
21 | output = 'src/lib/hooks'
22 | target = 'svelte'
23 | }
24 |
25 | /*
26 | * Model for a space in which users can collaborate on Lists and Todos
27 | */
28 | model Space {
29 | id String @id @default(uuid())
30 | createdAt DateTime @default(now())
31 | updatedAt DateTime @updatedAt
32 | owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
33 | ownerId String @default(auth().id)
34 | name String @length(4, 50)
35 | slug String @unique @regex('^[0-9a-zA-Z\-_]{4,16}$')
36 | members SpaceUser[]
37 | lists List[]
38 |
39 | // require login
40 | @@deny('all', auth() == null)
41 |
42 | // everyone can create a space
43 | @@allow('create', true)
44 |
45 | // any user in the space can read the space
46 | @@allow('read', members?[user == auth()])
47 |
48 | // space admin can update and delete
49 | @@allow('update,delete', members?[user == auth() && role == ADMIN])
50 | }
51 |
52 | /*
53 | * Model representing membership of a user in a space
54 | */
55 | model SpaceUser {
56 | id String @id @default(uuid())
57 | createdAt DateTime @default(now())
58 | updatedAt DateTime @updatedAt
59 | space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
60 | spaceId String
61 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
62 | userId String
63 | role SpaceUserRole
64 | @@unique([userId, spaceId])
65 |
66 | // require login
67 | @@deny('all', auth() == null)
68 |
69 | // space owner can add any one
70 | @@allow('create', space.owner == auth())
71 |
72 | // space admin can add anyone but not himself
73 | @@allow('create', auth() != user && space.members?[user == auth() && role == ADMIN])
74 |
75 | // space admin can update/delete
76 | @@allow('update,delete', space.members?[user == auth() && role == ADMIN])
77 |
78 | // user can read entries for spaces which he's a member of
79 | @@allow('read', space.members?[user == auth()])
80 | }
81 |
82 | /*
83 | * User model
84 | */
85 | model User {
86 | id String @id @default(cuid())
87 | email String @unique @email
88 | password String @password @omit @length(6, 32)
89 | name String?
90 | ownedSpaces Space[]
91 | memberships SpaceUser[]
92 | todos Todo[]
93 | lists List[]
94 | @@allow('create,read', true)
95 | @@allow('all', auth() == this)
96 | }
97 |
98 | /*
99 | * Model for a Todo list
100 | */
101 | model List {
102 | id String @id @default(uuid())
103 | createdAt DateTime @default(now())
104 | updatedAt DateTime @updatedAt
105 | space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
106 | spaceId String
107 | owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
108 | ownerId String @default(auth().id)
109 | title String @length(1, 100)
110 | private Boolean @default(false)
111 | todos Todo[]
112 |
113 | // require login
114 | @@deny('all', auth() == null)
115 |
116 | // can be read by owner or space members (only if not private)
117 | @@allow('read', owner == auth() || (space.members?[user == auth()] && !private))
118 |
119 | // when create, owner must be set to current user, and user must be in the space
120 | @@allow('create', owner == auth() && space.members?[user == auth()])
121 |
122 | // when create, owner must be set to current user, and user must be in the space
123 | // update is not allowed to change owner
124 | @@allow('update', owner == auth() && space.members?[user == auth()] && future().owner == owner)
125 |
126 | // can be deleted by owner
127 | @@allow('delete', owner == auth())
128 | }
129 |
130 | /*
131 | * Model for a single Todo
132 | */
133 | model Todo {
134 | id String @id @default(uuid())
135 | createdAt DateTime @default(now())
136 | updatedAt DateTime @updatedAt
137 | owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
138 | ownerId String @default(auth().id)
139 | list List @relation(fields: [listId], references: [id], onDelete: Cascade)
140 | listId String
141 | title String @length(1, 100)
142 | completedAt DateTime?
143 |
144 | // require login
145 | @@deny('all', auth() == null)
146 |
147 | // owner has full access, also space members have full access (if the parent List is not private)
148 | @@allow('all', list.owner == auth())
149 | @@allow('all', list.space.members?[user == auth()] && !list.private)
150 |
151 | // update cannot change owner
152 | @@deny('update', future().owner != owner)
153 | }
154 |
--------------------------------------------------------------------------------
/src/lib/hooks/__model_meta.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 |
7 | import type { ModelMeta } from "@zenstackhq/runtime";
8 |
9 | const metadata: ModelMeta = {
10 | models: {
11 | space: {
12 | name: 'Space', fields: {
13 | id: {
14 | name: "id",
15 | type: "String",
16 | isId: true,
17 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
18 | }, createdAt: {
19 | name: "createdAt",
20 | type: "DateTime",
21 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
22 | }, updatedAt: {
23 | name: "updatedAt",
24 | type: "DateTime",
25 | attributes: [{ "name": "@updatedAt", "args": [] }],
26 | }, owner: {
27 | name: "owner",
28 | type: "User",
29 | isDataModel: true,
30 | backLink: 'ownedSpaces',
31 | isRelationOwner: true,
32 | onDeleteAction: 'Cascade',
33 | foreignKeyMapping: { "id": "ownerId" },
34 | }, ownerId: {
35 | name: "ownerId",
36 | type: "String",
37 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
38 | defaultValueProvider: $default$Space$ownerId,
39 | isForeignKey: true,
40 | relationField: 'owner',
41 | }, name: {
42 | name: "name",
43 | type: "String",
44 | }, slug: {
45 | name: "slug",
46 | type: "String",
47 | }, members: {
48 | name: "members",
49 | type: "SpaceUser",
50 | isDataModel: true,
51 | isArray: true,
52 | backLink: 'space',
53 | }, lists: {
54 | name: "lists",
55 | type: "List",
56 | isDataModel: true,
57 | isArray: true,
58 | backLink: 'space',
59 | },
60 | }, uniqueConstraints: {
61 | id: {
62 | name: "id",
63 | fields: ["id"]
64 | }, slug: {
65 | name: "slug",
66 | fields: ["slug"]
67 | },
68 | },
69 | },
70 | spaceUser: {
71 | name: 'SpaceUser', fields: {
72 | id: {
73 | name: "id",
74 | type: "String",
75 | isId: true,
76 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
77 | }, createdAt: {
78 | name: "createdAt",
79 | type: "DateTime",
80 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
81 | }, updatedAt: {
82 | name: "updatedAt",
83 | type: "DateTime",
84 | attributes: [{ "name": "@updatedAt", "args": [] }],
85 | }, space: {
86 | name: "space",
87 | type: "Space",
88 | isDataModel: true,
89 | backLink: 'members',
90 | isRelationOwner: true,
91 | onDeleteAction: 'Cascade',
92 | foreignKeyMapping: { "id": "spaceId" },
93 | }, spaceId: {
94 | name: "spaceId",
95 | type: "String",
96 | isForeignKey: true,
97 | relationField: 'space',
98 | }, user: {
99 | name: "user",
100 | type: "User",
101 | isDataModel: true,
102 | backLink: 'memberships',
103 | isRelationOwner: true,
104 | onDeleteAction: 'Cascade',
105 | foreignKeyMapping: { "id": "userId" },
106 | }, userId: {
107 | name: "userId",
108 | type: "String",
109 | isForeignKey: true,
110 | relationField: 'user',
111 | }, role: {
112 | name: "role",
113 | type: "SpaceUserRole",
114 | },
115 | }, uniqueConstraints: {
116 | id: {
117 | name: "id",
118 | fields: ["id"]
119 | }, userId_spaceId: {
120 | name: "userId_spaceId",
121 | fields: ["userId", "spaceId"]
122 | },
123 | },
124 | },
125 | user: {
126 | name: 'User', fields: {
127 | id: {
128 | name: "id",
129 | type: "String",
130 | isId: true,
131 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
132 | }, email: {
133 | name: "email",
134 | type: "String",
135 | }, password: {
136 | name: "password",
137 | type: "String",
138 | }, name: {
139 | name: "name",
140 | type: "String",
141 | isOptional: true,
142 | }, ownedSpaces: {
143 | name: "ownedSpaces",
144 | type: "Space",
145 | isDataModel: true,
146 | isArray: true,
147 | backLink: 'owner',
148 | }, memberships: {
149 | name: "memberships",
150 | type: "SpaceUser",
151 | isDataModel: true,
152 | isArray: true,
153 | backLink: 'user',
154 | }, todos: {
155 | name: "todos",
156 | type: "Todo",
157 | isDataModel: true,
158 | isArray: true,
159 | backLink: 'owner',
160 | }, lists: {
161 | name: "lists",
162 | type: "List",
163 | isDataModel: true,
164 | isArray: true,
165 | backLink: 'owner',
166 | },
167 | }, uniqueConstraints: {
168 | id: {
169 | name: "id",
170 | fields: ["id"]
171 | }, email: {
172 | name: "email",
173 | fields: ["email"]
174 | },
175 | },
176 | },
177 | list: {
178 | name: 'List', fields: {
179 | id: {
180 | name: "id",
181 | type: "String",
182 | isId: true,
183 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
184 | }, createdAt: {
185 | name: "createdAt",
186 | type: "DateTime",
187 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
188 | }, updatedAt: {
189 | name: "updatedAt",
190 | type: "DateTime",
191 | attributes: [{ "name": "@updatedAt", "args": [] }],
192 | }, space: {
193 | name: "space",
194 | type: "Space",
195 | isDataModel: true,
196 | backLink: 'lists',
197 | isRelationOwner: true,
198 | onDeleteAction: 'Cascade',
199 | foreignKeyMapping: { "id": "spaceId" },
200 | }, spaceId: {
201 | name: "spaceId",
202 | type: "String",
203 | isForeignKey: true,
204 | relationField: 'space',
205 | }, owner: {
206 | name: "owner",
207 | type: "User",
208 | isDataModel: true,
209 | backLink: 'lists',
210 | isRelationOwner: true,
211 | onDeleteAction: 'Cascade',
212 | foreignKeyMapping: { "id": "ownerId" },
213 | }, ownerId: {
214 | name: "ownerId",
215 | type: "String",
216 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
217 | defaultValueProvider: $default$List$ownerId,
218 | isForeignKey: true,
219 | relationField: 'owner',
220 | }, title: {
221 | name: "title",
222 | type: "String",
223 | }, private: {
224 | name: "private",
225 | type: "Boolean",
226 | attributes: [{ "name": "@default", "args": [{ "name": "value", "value": false }] }],
227 | }, todos: {
228 | name: "todos",
229 | type: "Todo",
230 | isDataModel: true,
231 | isArray: true,
232 | backLink: 'list',
233 | },
234 | }, uniqueConstraints: {
235 | id: {
236 | name: "id",
237 | fields: ["id"]
238 | },
239 | },
240 | },
241 | todo: {
242 | name: 'Todo', fields: {
243 | id: {
244 | name: "id",
245 | type: "String",
246 | isId: true,
247 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
248 | }, createdAt: {
249 | name: "createdAt",
250 | type: "DateTime",
251 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
252 | }, updatedAt: {
253 | name: "updatedAt",
254 | type: "DateTime",
255 | attributes: [{ "name": "@updatedAt", "args": [] }],
256 | }, owner: {
257 | name: "owner",
258 | type: "User",
259 | isDataModel: true,
260 | backLink: 'todos',
261 | isRelationOwner: true,
262 | onDeleteAction: 'Cascade',
263 | foreignKeyMapping: { "id": "ownerId" },
264 | }, ownerId: {
265 | name: "ownerId",
266 | type: "String",
267 | attributes: [{ "name": "@default", "args": [{ "name": "value" }] }],
268 | defaultValueProvider: $default$Todo$ownerId,
269 | isForeignKey: true,
270 | relationField: 'owner',
271 | }, list: {
272 | name: "list",
273 | type: "List",
274 | isDataModel: true,
275 | backLink: 'todos',
276 | isRelationOwner: true,
277 | onDeleteAction: 'Cascade',
278 | foreignKeyMapping: { "id": "listId" },
279 | }, listId: {
280 | name: "listId",
281 | type: "String",
282 | isForeignKey: true,
283 | relationField: 'list',
284 | }, title: {
285 | name: "title",
286 | type: "String",
287 | }, completedAt: {
288 | name: "completedAt",
289 | type: "DateTime",
290 | isOptional: true,
291 | },
292 | }, uniqueConstraints: {
293 | id: {
294 | name: "id",
295 | fields: ["id"]
296 | },
297 | },
298 | },
299 |
300 | },
301 | deleteCascade: {
302 | space: ['SpaceUser', 'List'],
303 | user: ['Space', 'SpaceUser', 'List', 'Todo'],
304 | list: ['Todo'],
305 |
306 | },
307 | authModel: 'User'
308 |
309 | };
310 |
311 | function $default$Space$ownerId(user: any): unknown {
312 | return user?.id;
313 | }
314 |
315 | function $default$List$ownerId(user: any): unknown {
316 | return user?.id;
317 | }
318 |
319 | function $default$Todo$ownerId(user: any): unknown {
320 | return user?.id;
321 | }
322 | export default metadata;
323 |
--------------------------------------------------------------------------------
/src/lib/hooks/todo.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 |
7 | import type { Prisma, Todo } from "@zenstackhq/runtime/models";
8 | import { derived } from 'svelte/store';
9 | import type { MutationOptions, CreateQueryOptions, CreateInfiniteQueryOptions } from '@tanstack/svelte-query';
10 | import type { InfiniteData, StoreOrVal } from '@tanstack/svelte-query';
11 | import { getHooksContext } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
12 | import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
13 | import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '@zenstackhq/tanstack-query/runtime-v5';
14 | import type { PolicyCrudKind } from '@zenstackhq/runtime'
15 | import metadata from './__model_meta';
16 | type DefaultError = QueryError;
17 |
18 | export function useCreateTodo(options?: Omit<(MutationOptions<(Todo | undefined), DefaultError, Prisma.TodoCreateArgs> & ExtraMutationOptions), 'mutationFn'>) {
19 | const { endpoint, fetch } = getHooksContext();
20 | const _mutation =
21 | useModelMutation('Todo', 'POST', `${endpoint}/todo/create`, metadata, options, fetch, true)
22 | ;
23 | const mutation = derived(_mutation, value => ({
24 | ...value,
25 | mutateAsync: async (
26 | args: Prisma.SelectSubset,
27 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
28 | ) => {
29 | return (await value.mutateAsync(
30 | args,
31 | options as any
32 | )) as (CheckSelect> | undefined);
33 | },
34 | }));
35 | return mutation;
36 | }
37 |
38 | export function useCreateManyTodo(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
39 | const { endpoint, fetch } = getHooksContext();
40 | const _mutation =
41 | useModelMutation('Todo', 'POST', `${endpoint}/todo/createMany`, metadata, options, fetch, false)
42 | ;
43 | const mutation = derived(_mutation, value => ({
44 | ...value,
45 | mutateAsync: async (
46 | args: Prisma.SelectSubset,
47 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
48 | ) => {
49 | return (await value.mutateAsync(
50 | args,
51 | options as any
52 | )) as Prisma.BatchPayload;
53 | },
54 | }));
55 | return mutation;
56 | }
57 |
58 | export function useFindManyTodo & { $optimistic?: boolean }>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
59 | const { endpoint, fetch } = getHooksContext();
60 | return useModelQuery('Todo', `${endpoint}/todo/findMany`, args, options, fetch);
61 | }
62 |
63 | export function useInfiniteFindManyTodo>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: StoreOrVal>, 'queryKey' | 'initialPageParam'>>) {
64 | options = options ?? { getNextPageParam: () => null };
65 | const { endpoint, fetch } = getHooksContext();
66 | return useInfiniteModelQuery('Todo', `${endpoint}/todo/findMany`, args, options, fetch);
67 | }
68 |
69 | export function useFindUniqueTodo & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
70 | const { endpoint, fetch } = getHooksContext();
71 | return useModelQuery('Todo', `${endpoint}/todo/findUnique`, args, options, fetch);
72 | }
73 |
74 | export function useFindFirstTodo & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
75 | const { endpoint, fetch } = getHooksContext();
76 | return useModelQuery('Todo', `${endpoint}/todo/findFirst`, args, options, fetch);
77 | }
78 |
79 | export function useUpdateTodo(options?: Omit<(MutationOptions<(Todo | undefined), DefaultError, Prisma.TodoUpdateArgs> & ExtraMutationOptions), 'mutationFn'>) {
80 | const { endpoint, fetch } = getHooksContext();
81 | const _mutation =
82 | useModelMutation('Todo', 'PUT', `${endpoint}/todo/update`, metadata, options, fetch, true)
83 | ;
84 | const mutation = derived(_mutation, value => ({
85 | ...value,
86 | mutateAsync: async (
87 | args: Prisma.SelectSubset,
88 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
89 | ) => {
90 | return (await value.mutateAsync(
91 | args,
92 | options as any
93 | )) as (CheckSelect> | undefined);
94 | },
95 | }));
96 | return mutation;
97 | }
98 |
99 | export function useUpdateManyTodo(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
100 | const { endpoint, fetch } = getHooksContext();
101 | const _mutation =
102 | useModelMutation('Todo', 'PUT', `${endpoint}/todo/updateMany`, metadata, options, fetch, false)
103 | ;
104 | const mutation = derived(_mutation, value => ({
105 | ...value,
106 | mutateAsync: async (
107 | args: Prisma.SelectSubset,
108 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
109 | ) => {
110 | return (await value.mutateAsync(
111 | args,
112 | options as any
113 | )) as Prisma.BatchPayload;
114 | },
115 | }));
116 | return mutation;
117 | }
118 |
119 | export function useUpsertTodo(options?: Omit<(MutationOptions<(Todo | undefined), DefaultError, Prisma.TodoUpsertArgs> & ExtraMutationOptions), 'mutationFn'>) {
120 | const { endpoint, fetch } = getHooksContext();
121 | const _mutation =
122 | useModelMutation('Todo', 'POST', `${endpoint}/todo/upsert`, metadata, options, fetch, true)
123 | ;
124 | const mutation = derived(_mutation, value => ({
125 | ...value,
126 | mutateAsync: async (
127 | args: Prisma.SelectSubset,
128 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
129 | ) => {
130 | return (await value.mutateAsync(
131 | args,
132 | options as any
133 | )) as (CheckSelect> | undefined);
134 | },
135 | }));
136 | return mutation;
137 | }
138 |
139 | export function useDeleteTodo(options?: Omit<(MutationOptions<(Todo | undefined), DefaultError, Prisma.TodoDeleteArgs> & ExtraMutationOptions), 'mutationFn'>) {
140 | const { endpoint, fetch } = getHooksContext();
141 | const _mutation =
142 | useModelMutation('Todo', 'DELETE', `${endpoint}/todo/delete`, metadata, options, fetch, true)
143 | ;
144 | const mutation = derived(_mutation, value => ({
145 | ...value,
146 | mutateAsync: async (
147 | args: Prisma.SelectSubset,
148 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
149 | ) => {
150 | return (await value.mutateAsync(
151 | args,
152 | options as any
153 | )) as (CheckSelect> | undefined);
154 | },
155 | }));
156 | return mutation;
157 | }
158 |
159 | export function useDeleteManyTodo(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
160 | const { endpoint, fetch } = getHooksContext();
161 | const _mutation =
162 | useModelMutation('Todo', 'DELETE', `${endpoint}/todo/deleteMany`, metadata, options, fetch, false)
163 | ;
164 | const mutation = derived(_mutation, value => ({
165 | ...value,
166 | mutateAsync: async (
167 | args: Prisma.SelectSubset,
168 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
169 | ) => {
170 | return (await value.mutateAsync(
171 | args,
172 | options as any
173 | )) as Prisma.BatchPayload;
174 | },
175 | }));
176 | return mutation;
177 | }
178 |
179 | export function useAggregateTodo, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
180 | const { endpoint, fetch } = getHooksContext();
181 | return useModelQuery('Todo', `${endpoint}/todo/aggregate`, args, options, fetch);
182 | }
183 |
184 | export function useGroupByTodo>, Prisma.Extends<'take', Prisma.Keys>>, OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.TodoGroupByArgs['orderBy'] } : { orderBy?: Prisma.TodoGroupByArgs['orderBy'] }, OrderFields extends Prisma.ExcludeUnderscoreKeys>>, ByFields extends Prisma.MaybeTupleToUnion, ByValid extends Prisma.Has, HavingFields extends Prisma.GetHavingFields, HavingValid extends Prisma.Has, ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False, InputErrors extends ByEmpty extends Prisma.True
185 | ? `Error: "by" must not be empty.`
186 | : HavingValid extends Prisma.False
187 | ? {
188 | [P in HavingFields]: P extends ByFields
189 | ? never
190 | : P extends string
191 | ? `Error: Field "${P}" used in "having" needs to be provided in "by".`
192 | : [
193 | Error,
194 | 'Field ',
195 | P,
196 | ` in "having" needs to be provided in "by"`,
197 | ]
198 | }[HavingFields]
199 | : 'take' extends Prisma.Keys
200 | ? 'orderBy' extends Prisma.Keys
201 | ? ByValid extends Prisma.True
202 | ? {}
203 | : {
204 | [P in OrderFields]: P extends ByFields
205 | ? never
206 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
207 | }[OrderFields]
208 | : 'Error: If you provide "take", you also need to provide "orderBy"'
209 | : 'skip' extends Prisma.Keys
210 | ? 'orderBy' extends Prisma.Keys
211 | ? ByValid extends Prisma.True
212 | ? {}
213 | : {
214 | [P in OrderFields]: P extends ByFields
215 | ? never
216 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
217 | }[OrderFields]
218 | : 'Error: If you provide "skip", you also need to provide "orderBy"'
219 | : ByValid extends Prisma.True
220 | ? {}
221 | : {
222 | [P in OrderFields]: P extends ByFields
223 | ? never
224 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
225 | }[OrderFields], TQueryFnData = {} extends InputErrors ?
226 | Array &
227 | {
228 | [P in ((keyof TArgs) & (keyof Prisma.TodoGroupByOutputType))]: P extends '_count'
229 | ? TArgs[P] extends boolean
230 | ? number
231 | : Prisma.GetScalarType
232 | : Prisma.GetScalarType
233 | }
234 | > : InputErrors, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset & InputErrors>, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
235 | const { endpoint, fetch } = getHooksContext();
236 | return useModelQuery('Todo', `${endpoint}/todo/groupBy`, args, options, fetch);
237 | }
238 |
239 | export function useCountTodo : number, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
240 | const { endpoint, fetch } = getHooksContext();
241 | return useModelQuery('Todo', `${endpoint}/todo/count`, args, options, fetch);
242 | }
243 |
244 | export function useCheckTodo(args: { operation: PolicyCrudKind; where?: { id?: string; ownerId?: string; listId?: string; title?: string }; }, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
245 | const { endpoint, fetch } = getHooksContext();
246 | return useModelQuery('Todo', `${endpoint}/todo/check`, args, options, fetch);
247 | }
248 |
--------------------------------------------------------------------------------
/src/lib/hooks/user.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 |
7 | import type { Prisma, User } from "@zenstackhq/runtime/models";
8 | import { derived } from 'svelte/store';
9 | import type { MutationOptions, CreateQueryOptions, CreateInfiniteQueryOptions } from '@tanstack/svelte-query';
10 | import type { InfiniteData, StoreOrVal } from '@tanstack/svelte-query';
11 | import { getHooksContext } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
12 | import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
13 | import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '@zenstackhq/tanstack-query/runtime-v5';
14 | import type { PolicyCrudKind } from '@zenstackhq/runtime'
15 | import metadata from './__model_meta';
16 | type DefaultError = QueryError;
17 |
18 | export function useCreateUser(options?: Omit<(MutationOptions<(User | undefined), DefaultError, Prisma.UserCreateArgs> & ExtraMutationOptions), 'mutationFn'>) {
19 | const { endpoint, fetch } = getHooksContext();
20 | const _mutation =
21 | useModelMutation('User', 'POST', `${endpoint}/user/create`, metadata, options, fetch, true)
22 | ;
23 | const mutation = derived(_mutation, value => ({
24 | ...value,
25 | mutateAsync: async (
26 | args: Prisma.SelectSubset,
27 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
28 | ) => {
29 | return (await value.mutateAsync(
30 | args,
31 | options as any
32 | )) as (CheckSelect> | undefined);
33 | },
34 | }));
35 | return mutation;
36 | }
37 |
38 | export function useCreateManyUser(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
39 | const { endpoint, fetch } = getHooksContext();
40 | const _mutation =
41 | useModelMutation('User', 'POST', `${endpoint}/user/createMany`, metadata, options, fetch, false)
42 | ;
43 | const mutation = derived(_mutation, value => ({
44 | ...value,
45 | mutateAsync: async (
46 | args: Prisma.SelectSubset,
47 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
48 | ) => {
49 | return (await value.mutateAsync(
50 | args,
51 | options as any
52 | )) as Prisma.BatchPayload;
53 | },
54 | }));
55 | return mutation;
56 | }
57 |
58 | export function useFindManyUser & { $optimistic?: boolean }>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
59 | const { endpoint, fetch } = getHooksContext();
60 | return useModelQuery('User', `${endpoint}/user/findMany`, args, options, fetch);
61 | }
62 |
63 | export function useInfiniteFindManyUser>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: StoreOrVal>, 'queryKey' | 'initialPageParam'>>) {
64 | options = options ?? { getNextPageParam: () => null };
65 | const { endpoint, fetch } = getHooksContext();
66 | return useInfiniteModelQuery('User', `${endpoint}/user/findMany`, args, options, fetch);
67 | }
68 |
69 | export function useFindUniqueUser & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
70 | const { endpoint, fetch } = getHooksContext();
71 | return useModelQuery('User', `${endpoint}/user/findUnique`, args, options, fetch);
72 | }
73 |
74 | export function useFindFirstUser & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
75 | const { endpoint, fetch } = getHooksContext();
76 | return useModelQuery('User', `${endpoint}/user/findFirst`, args, options, fetch);
77 | }
78 |
79 | export function useUpdateUser(options?: Omit<(MutationOptions<(User | undefined), DefaultError, Prisma.UserUpdateArgs> & ExtraMutationOptions), 'mutationFn'>) {
80 | const { endpoint, fetch } = getHooksContext();
81 | const _mutation =
82 | useModelMutation('User', 'PUT', `${endpoint}/user/update`, metadata, options, fetch, true)
83 | ;
84 | const mutation = derived(_mutation, value => ({
85 | ...value,
86 | mutateAsync: async (
87 | args: Prisma.SelectSubset,
88 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
89 | ) => {
90 | return (await value.mutateAsync(
91 | args,
92 | options as any
93 | )) as (CheckSelect> | undefined);
94 | },
95 | }));
96 | return mutation;
97 | }
98 |
99 | export function useUpdateManyUser(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
100 | const { endpoint, fetch } = getHooksContext();
101 | const _mutation =
102 | useModelMutation('User', 'PUT', `${endpoint}/user/updateMany`, metadata, options, fetch, false)
103 | ;
104 | const mutation = derived(_mutation, value => ({
105 | ...value,
106 | mutateAsync: async (
107 | args: Prisma.SelectSubset,
108 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
109 | ) => {
110 | return (await value.mutateAsync(
111 | args,
112 | options as any
113 | )) as Prisma.BatchPayload;
114 | },
115 | }));
116 | return mutation;
117 | }
118 |
119 | export function useUpsertUser(options?: Omit<(MutationOptions<(User | undefined), DefaultError, Prisma.UserUpsertArgs> & ExtraMutationOptions), 'mutationFn'>) {
120 | const { endpoint, fetch } = getHooksContext();
121 | const _mutation =
122 | useModelMutation('User', 'POST', `${endpoint}/user/upsert`, metadata, options, fetch, true)
123 | ;
124 | const mutation = derived(_mutation, value => ({
125 | ...value,
126 | mutateAsync: async (
127 | args: Prisma.SelectSubset,
128 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
129 | ) => {
130 | return (await value.mutateAsync(
131 | args,
132 | options as any
133 | )) as (CheckSelect> | undefined);
134 | },
135 | }));
136 | return mutation;
137 | }
138 |
139 | export function useDeleteUser(options?: Omit<(MutationOptions<(User | undefined), DefaultError, Prisma.UserDeleteArgs> & ExtraMutationOptions), 'mutationFn'>) {
140 | const { endpoint, fetch } = getHooksContext();
141 | const _mutation =
142 | useModelMutation('User', 'DELETE', `${endpoint}/user/delete`, metadata, options, fetch, true)
143 | ;
144 | const mutation = derived(_mutation, value => ({
145 | ...value,
146 | mutateAsync: async (
147 | args: Prisma.SelectSubset,
148 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
149 | ) => {
150 | return (await value.mutateAsync(
151 | args,
152 | options as any
153 | )) as (CheckSelect> | undefined);
154 | },
155 | }));
156 | return mutation;
157 | }
158 |
159 | export function useDeleteManyUser(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
160 | const { endpoint, fetch } = getHooksContext();
161 | const _mutation =
162 | useModelMutation('User', 'DELETE', `${endpoint}/user/deleteMany`, metadata, options, fetch, false)
163 | ;
164 | const mutation = derived(_mutation, value => ({
165 | ...value,
166 | mutateAsync: async (
167 | args: Prisma.SelectSubset,
168 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
169 | ) => {
170 | return (await value.mutateAsync(
171 | args,
172 | options as any
173 | )) as Prisma.BatchPayload;
174 | },
175 | }));
176 | return mutation;
177 | }
178 |
179 | export function useAggregateUser, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
180 | const { endpoint, fetch } = getHooksContext();
181 | return useModelQuery('User', `${endpoint}/user/aggregate`, args, options, fetch);
182 | }
183 |
184 | export function useGroupByUser>, Prisma.Extends<'take', Prisma.Keys>>, OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.UserGroupByArgs['orderBy'] } : { orderBy?: Prisma.UserGroupByArgs['orderBy'] }, OrderFields extends Prisma.ExcludeUnderscoreKeys>>, ByFields extends Prisma.MaybeTupleToUnion, ByValid extends Prisma.Has, HavingFields extends Prisma.GetHavingFields, HavingValid extends Prisma.Has, ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False, InputErrors extends ByEmpty extends Prisma.True
185 | ? `Error: "by" must not be empty.`
186 | : HavingValid extends Prisma.False
187 | ? {
188 | [P in HavingFields]: P extends ByFields
189 | ? never
190 | : P extends string
191 | ? `Error: Field "${P}" used in "having" needs to be provided in "by".`
192 | : [
193 | Error,
194 | 'Field ',
195 | P,
196 | ` in "having" needs to be provided in "by"`,
197 | ]
198 | }[HavingFields]
199 | : 'take' extends Prisma.Keys
200 | ? 'orderBy' extends Prisma.Keys
201 | ? ByValid extends Prisma.True
202 | ? {}
203 | : {
204 | [P in OrderFields]: P extends ByFields
205 | ? never
206 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
207 | }[OrderFields]
208 | : 'Error: If you provide "take", you also need to provide "orderBy"'
209 | : 'skip' extends Prisma.Keys
210 | ? 'orderBy' extends Prisma.Keys
211 | ? ByValid extends Prisma.True
212 | ? {}
213 | : {
214 | [P in OrderFields]: P extends ByFields
215 | ? never
216 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
217 | }[OrderFields]
218 | : 'Error: If you provide "skip", you also need to provide "orderBy"'
219 | : ByValid extends Prisma.True
220 | ? {}
221 | : {
222 | [P in OrderFields]: P extends ByFields
223 | ? never
224 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
225 | }[OrderFields], TQueryFnData = {} extends InputErrors ?
226 | Array &
227 | {
228 | [P in ((keyof TArgs) & (keyof Prisma.UserGroupByOutputType))]: P extends '_count'
229 | ? TArgs[P] extends boolean
230 | ? number
231 | : Prisma.GetScalarType
232 | : Prisma.GetScalarType
233 | }
234 | > : InputErrors, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset & InputErrors>, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
235 | const { endpoint, fetch } = getHooksContext();
236 | return useModelQuery('User', `${endpoint}/user/groupBy`, args, options, fetch);
237 | }
238 |
239 | export function useCountUser : number, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
240 | const { endpoint, fetch } = getHooksContext();
241 | return useModelQuery('User', `${endpoint}/user/count`, args, options, fetch);
242 | }
243 |
244 | export function useCheckUser(args: { operation: PolicyCrudKind; where?: { id?: string; email?: string; password?: string; name?: string }; }, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
245 | const { endpoint, fetch } = getHooksContext();
246 | return useModelQuery('User', `${endpoint}/user/check`, args, options, fetch);
247 | }
248 |
--------------------------------------------------------------------------------
/src/lib/hooks/list.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 |
7 | import type { Prisma, List } from "@zenstackhq/runtime/models";
8 | import { derived } from 'svelte/store';
9 | import type { MutationOptions, CreateQueryOptions, CreateInfiniteQueryOptions } from '@tanstack/svelte-query';
10 | import type { InfiniteData, StoreOrVal } from '@tanstack/svelte-query';
11 | import { getHooksContext } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
12 | import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
13 | import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '@zenstackhq/tanstack-query/runtime-v5';
14 | import type { PolicyCrudKind } from '@zenstackhq/runtime'
15 | import metadata from './__model_meta';
16 | type DefaultError = QueryError;
17 |
18 | export function useCreateList(options?: Omit<(MutationOptions<(List | undefined), DefaultError, Prisma.ListCreateArgs> & ExtraMutationOptions), 'mutationFn'>) {
19 | const { endpoint, fetch } = getHooksContext();
20 | const _mutation =
21 | useModelMutation('List', 'POST', `${endpoint}/list/create`, metadata, options, fetch, true)
22 | ;
23 | const mutation = derived(_mutation, value => ({
24 | ...value,
25 | mutateAsync: async (
26 | args: Prisma.SelectSubset,
27 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
28 | ) => {
29 | return (await value.mutateAsync(
30 | args,
31 | options as any
32 | )) as (CheckSelect> | undefined);
33 | },
34 | }));
35 | return mutation;
36 | }
37 |
38 | export function useCreateManyList(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
39 | const { endpoint, fetch } = getHooksContext();
40 | const _mutation =
41 | useModelMutation('List', 'POST', `${endpoint}/list/createMany`, metadata, options, fetch, false)
42 | ;
43 | const mutation = derived(_mutation, value => ({
44 | ...value,
45 | mutateAsync: async (
46 | args: Prisma.SelectSubset,
47 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
48 | ) => {
49 | return (await value.mutateAsync(
50 | args,
51 | options as any
52 | )) as Prisma.BatchPayload;
53 | },
54 | }));
55 | return mutation;
56 | }
57 |
58 | export function useFindManyList & { $optimistic?: boolean }>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
59 | const { endpoint, fetch } = getHooksContext();
60 | return useModelQuery('List', `${endpoint}/list/findMany`, args, options, fetch);
61 | }
62 |
63 | export function useInfiniteFindManyList>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: StoreOrVal>, 'queryKey' | 'initialPageParam'>>) {
64 | options = options ?? { getNextPageParam: () => null };
65 | const { endpoint, fetch } = getHooksContext();
66 | return useInfiniteModelQuery('List', `${endpoint}/list/findMany`, args, options, fetch);
67 | }
68 |
69 | export function useFindUniqueList & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
70 | const { endpoint, fetch } = getHooksContext();
71 | return useModelQuery('List', `${endpoint}/list/findUnique`, args, options, fetch);
72 | }
73 |
74 | export function useFindFirstList & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
75 | const { endpoint, fetch } = getHooksContext();
76 | return useModelQuery('List', `${endpoint}/list/findFirst`, args, options, fetch);
77 | }
78 |
79 | export function useUpdateList(options?: Omit<(MutationOptions<(List | undefined), DefaultError, Prisma.ListUpdateArgs> & ExtraMutationOptions), 'mutationFn'>) {
80 | const { endpoint, fetch } = getHooksContext();
81 | const _mutation =
82 | useModelMutation('List', 'PUT', `${endpoint}/list/update`, metadata, options, fetch, true)
83 | ;
84 | const mutation = derived(_mutation, value => ({
85 | ...value,
86 | mutateAsync: async (
87 | args: Prisma.SelectSubset,
88 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
89 | ) => {
90 | return (await value.mutateAsync(
91 | args,
92 | options as any
93 | )) as (CheckSelect> | undefined);
94 | },
95 | }));
96 | return mutation;
97 | }
98 |
99 | export function useUpdateManyList(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
100 | const { endpoint, fetch } = getHooksContext();
101 | const _mutation =
102 | useModelMutation('List', 'PUT', `${endpoint}/list/updateMany`, metadata, options, fetch, false)
103 | ;
104 | const mutation = derived(_mutation, value => ({
105 | ...value,
106 | mutateAsync: async (
107 | args: Prisma.SelectSubset,
108 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
109 | ) => {
110 | return (await value.mutateAsync(
111 | args,
112 | options as any
113 | )) as Prisma.BatchPayload;
114 | },
115 | }));
116 | return mutation;
117 | }
118 |
119 | export function useUpsertList(options?: Omit<(MutationOptions<(List | undefined), DefaultError, Prisma.ListUpsertArgs> & ExtraMutationOptions), 'mutationFn'>) {
120 | const { endpoint, fetch } = getHooksContext();
121 | const _mutation =
122 | useModelMutation('List', 'POST', `${endpoint}/list/upsert`, metadata, options, fetch, true)
123 | ;
124 | const mutation = derived(_mutation, value => ({
125 | ...value,
126 | mutateAsync: async (
127 | args: Prisma.SelectSubset,
128 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
129 | ) => {
130 | return (await value.mutateAsync(
131 | args,
132 | options as any
133 | )) as (CheckSelect> | undefined);
134 | },
135 | }));
136 | return mutation;
137 | }
138 |
139 | export function useDeleteList(options?: Omit<(MutationOptions<(List | undefined), DefaultError, Prisma.ListDeleteArgs> & ExtraMutationOptions), 'mutationFn'>) {
140 | const { endpoint, fetch } = getHooksContext();
141 | const _mutation =
142 | useModelMutation('List', 'DELETE', `${endpoint}/list/delete`, metadata, options, fetch, true)
143 | ;
144 | const mutation = derived(_mutation, value => ({
145 | ...value,
146 | mutateAsync: async (
147 | args: Prisma.SelectSubset,
148 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
149 | ) => {
150 | return (await value.mutateAsync(
151 | args,
152 | options as any
153 | )) as (CheckSelect> | undefined);
154 | },
155 | }));
156 | return mutation;
157 | }
158 |
159 | export function useDeleteManyList(options?: Omit<(MutationOptions & ExtraMutationOptions), 'mutationFn'>) {
160 | const { endpoint, fetch } = getHooksContext();
161 | const _mutation =
162 | useModelMutation('List', 'DELETE', `${endpoint}/list/deleteMany`, metadata, options, fetch, false)
163 | ;
164 | const mutation = derived(_mutation, value => ({
165 | ...value,
166 | mutateAsync: async (
167 | args: Prisma.SelectSubset,
168 | options?: Omit<(MutationOptions> & ExtraMutationOptions), 'mutationFn'>
169 | ) => {
170 | return (await value.mutateAsync(
171 | args,
172 | options as any
173 | )) as Prisma.BatchPayload;
174 | },
175 | }));
176 | return mutation;
177 | }
178 |
179 | export function useAggregateList, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
180 | const { endpoint, fetch } = getHooksContext();
181 | return useModelQuery('List', `${endpoint}/list/aggregate`, args, options, fetch);
182 | }
183 |
184 | export function useGroupByList>, Prisma.Extends<'take', Prisma.Keys>>, OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.ListGroupByArgs['orderBy'] } : { orderBy?: Prisma.ListGroupByArgs['orderBy'] }, OrderFields extends Prisma.ExcludeUnderscoreKeys>>, ByFields extends Prisma.MaybeTupleToUnion, ByValid extends Prisma.Has, HavingFields extends Prisma.GetHavingFields, HavingValid extends Prisma.Has, ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False, InputErrors extends ByEmpty extends Prisma.True
185 | ? `Error: "by" must not be empty.`
186 | : HavingValid extends Prisma.False
187 | ? {
188 | [P in HavingFields]: P extends ByFields
189 | ? never
190 | : P extends string
191 | ? `Error: Field "${P}" used in "having" needs to be provided in "by".`
192 | : [
193 | Error,
194 | 'Field ',
195 | P,
196 | ` in "having" needs to be provided in "by"`,
197 | ]
198 | }[HavingFields]
199 | : 'take' extends Prisma.Keys
200 | ? 'orderBy' extends Prisma.Keys
201 | ? ByValid extends Prisma.True
202 | ? {}
203 | : {
204 | [P in OrderFields]: P extends ByFields
205 | ? never
206 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
207 | }[OrderFields]
208 | : 'Error: If you provide "take", you also need to provide "orderBy"'
209 | : 'skip' extends Prisma.Keys
210 | ? 'orderBy' extends Prisma.Keys
211 | ? ByValid extends Prisma.True
212 | ? {}
213 | : {
214 | [P in OrderFields]: P extends ByFields
215 | ? never
216 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
217 | }[OrderFields]
218 | : 'Error: If you provide "skip", you also need to provide "orderBy"'
219 | : ByValid extends Prisma.True
220 | ? {}
221 | : {
222 | [P in OrderFields]: P extends ByFields
223 | ? never
224 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
225 | }[OrderFields], TQueryFnData = {} extends InputErrors ?
226 | Array &
227 | {
228 | [P in ((keyof TArgs) & (keyof Prisma.ListGroupByOutputType))]: P extends '_count'
229 | ? TArgs[P] extends boolean
230 | ? number
231 | : Prisma.GetScalarType
232 | : Prisma.GetScalarType
233 | }
234 | > : InputErrors, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset & InputErrors>, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
235 | const { endpoint, fetch } = getHooksContext();
236 | return useModelQuery('List', `${endpoint}/list/groupBy`, args, options, fetch);
237 | }
238 |
239 | export function useCountList : number, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
240 | const { endpoint, fetch } = getHooksContext();
241 | return useModelQuery('List', `${endpoint}/list/count`, args, options, fetch);
242 | }
243 |
244 | export function useCheckList(args: { operation: PolicyCrudKind; where?: { id?: string; spaceId?: string; ownerId?: string; title?: string; private?: boolean }; }, options?: (StoreOrVal, 'queryKey'>> & ExtraQueryOptions)) {
245 | const { endpoint, fetch } = getHooksContext();
246 | return useModelQuery('List', `${endpoint}/list/check`, args, options, fetch);
247 | }
248 |
--------------------------------------------------------------------------------
/src/lib/hooks/space.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 |
7 | import type { Prisma, Space } from "@zenstackhq/runtime/models";
8 | import { derived } from 'svelte/store';
9 | import type { MutationOptions, CreateQueryOptions, CreateInfiniteQueryOptions } from '@tanstack/svelte-query';
10 | import type { InfiniteData, StoreOrVal } from '@tanstack/svelte-query';
11 | import { getHooksContext } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
12 | import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '@zenstackhq/tanstack-query/runtime-v5/svelte';
13 | import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '@zenstackhq/tanstack-query/runtime-v5';
14 | import type { PolicyCrudKind } from '@zenstackhq/runtime'
15 | import metadata from './__model_meta';
16 | type DefaultError = QueryError;
17 |
18 | export function useCreateSpace(options?: Omit<(MutationOptions<(Space | undefined), DefaultError, Prisma.SpaceCreateArgs> & ExtraMutationOptions), 'mutationFn'>) {
19 | const { endpoint, fetch } = getHooksContext();
20 | const _mutation =
21 | useModelMutation('Space', 'POST', `${endpoint}/space/create`, metadata, options, fetch, true)
22 | ;
23 | const mutation = derived(_mutation, value => ({
24 | ...value,
25 | mutateAsync: async (
26 | args: Prisma.SelectSubset,
27 | options?: Omit<(MutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset