create-fate 1.0.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +3 -0
  2. package/bin/create-fate.mjs +594 -9
  3. package/package.json +2 -2
  4. package/templates/fate/README.md +3 -0
  5. package/templates/fate/_shared/vue-app/package.json +54 -0
  6. package/templates/fate/_shared/vue-app/pages/index.vue +57 -0
  7. package/templates/fate/_shared/vue-app/pages/layout.vue +32 -0
  8. package/templates/fate/_shared/vue-app/pages/post/[id].vue +24 -0
  9. package/templates/fate/_shared/vue-app/src/App.css +63 -0
  10. package/templates/fate/_shared/vue-app/src/fateViews.ts +14 -0
  11. package/templates/fate/_shared/vue-app/src/lib/cx.ts +6 -0
  12. package/templates/fate/_shared/vue-app/src/lib/env.ts +3 -0
  13. package/templates/fate/_shared/vue-app/src/ui/Button.vue +67 -0
  14. package/templates/fate/_shared/vue-app/src/ui/Card.vue +16 -0
  15. package/templates/fate/_shared/vue-app/src/ui/Error.vue +9 -0
  16. package/templates/fate/_shared/vue-app/src/ui/ErrorBoundary.vue +22 -0
  17. package/templates/fate/_shared/vue-app/src/ui/Header.vue +35 -0
  18. package/templates/fate/_shared/vue-app/src/ui/Link.vue +15 -0
  19. package/templates/fate/_shared/vue-app/src/ui/PostCard.vue +29 -0
  20. package/templates/fate/_shared/vue-app/src/ui/Section.vue +19 -0
  21. package/templates/fate/_shared/vue-app/src/ui/Thinking.vue +11 -0
  22. package/templates/fate/_shared/vue-app/src/ui/UserCard.vue +23 -0
  23. package/templates/fate/_shared/vue-app/src/vue-shims.d.ts +6 -0
  24. package/templates/fate/_shared/vue-app/tsconfig.json +22 -0
  25. package/templates/fate/_shared/vue-app/vite.config.ts +58 -0
  26. package/templates/fate/_shared/vue-app/void.json +5 -0
  27. package/templates/fate/cloudflare/.github/workflows/push.yml +27 -0
  28. package/templates/fate/cloudflare/.vite-hooks/pre-commit +1 -0
  29. package/templates/fate/cloudflare/.vscode/extensions.json +12 -0
  30. package/templates/fate/cloudflare/.vscode/settings.json +25 -0
  31. package/templates/fate/cloudflare/AGENTS.md +55 -0
  32. package/templates/fate/cloudflare/README.md +126 -0
  33. package/templates/fate/cloudflare/_gitignore +20 -0
  34. package/templates/fate/cloudflare/client/package.json +56 -0
  35. package/templates/fate/cloudflare/client/pages/index.tsx +96 -0
  36. package/templates/fate/cloudflare/client/pages/layout.tsx +82 -0
  37. package/templates/fate/cloudflare/client/pages/login.tsx +107 -0
  38. package/templates/fate/cloudflare/client/pages/post/[id].tsx +22 -0
  39. package/templates/fate/cloudflare/client/pages/search.tsx +99 -0
  40. package/templates/fate/cloudflare/client/public/icon.svg +1 -0
  41. package/templates/fate/cloudflare/client/src/App.css +153 -0
  42. package/templates/fate/cloudflare/client/src/lib/AvailableLanguages.tsx +5 -0
  43. package/templates/fate/cloudflare/client/src/lib/cx.tsx +6 -0
  44. package/templates/fate/cloudflare/client/src/lib/env.tsx +5 -0
  45. package/templates/fate/cloudflare/client/src/ui/Badge.tsx +31 -0
  46. package/templates/fate/cloudflare/client/src/ui/Button.tsx +108 -0
  47. package/templates/fate/cloudflare/client/src/ui/Card.tsx +32 -0
  48. package/templates/fate/cloudflare/client/src/ui/CommentCard.tsx +75 -0
  49. package/templates/fate/cloudflare/client/src/ui/Error.tsx +19 -0
  50. package/templates/fate/cloudflare/client/src/ui/H2.tsx +15 -0
  51. package/templates/fate/cloudflare/client/src/ui/H3.tsx +20 -0
  52. package/templates/fate/cloudflare/client/src/ui/Header.tsx +98 -0
  53. package/templates/fate/cloudflare/client/src/ui/Input.tsx +17 -0
  54. package/templates/fate/cloudflare/client/src/ui/Link.tsx +11 -0
  55. package/templates/fate/cloudflare/client/src/ui/LocaleSwitcher.tsx +52 -0
  56. package/templates/fate/cloudflare/client/src/ui/PostCard.tsx +303 -0
  57. package/templates/fate/cloudflare/client/src/ui/Section.tsx +23 -0
  58. package/templates/fate/cloudflare/client/src/ui/UserCard.tsx +131 -0
  59. package/templates/fate/cloudflare/client/src/user/AuthClient.tsx +6 -0
  60. package/templates/fate/cloudflare/client/translations/de_DE.json +689 -0
  61. package/templates/fate/cloudflare/client/translations/ja_JP.json +689 -0
  62. package/templates/fate/cloudflare/client/vite.config.ts +45 -0
  63. package/templates/fate/cloudflare/client/void.json +29 -0
  64. package/templates/fate/cloudflare/package.json +46 -0
  65. package/templates/fate/cloudflare/pnpm-workspace.yaml +39 -0
  66. package/templates/fate/cloudflare/server/.env.example +1 -0
  67. package/templates/fate/cloudflare/server/db/db.ts +30 -0
  68. package/templates/fate/cloudflare/server/db/migrations/20260508073357_initial_schema.sql +133 -0
  69. package/templates/fate/cloudflare/server/db/migrations/20260508120500_seed_cloudflare_demo.sql +170 -0
  70. package/templates/fate/cloudflare/server/db/migrations/meta/20260508073357_snapshot.json +868 -0
  71. package/templates/fate/cloudflare/server/db/migrations/meta/_journal.json +20 -0
  72. package/templates/fate/cloudflare/server/db/queries.ts +211 -0
  73. package/templates/fate/cloudflare/server/db/schema.ts +332 -0
  74. package/templates/fate/cloudflare/server/package.json +34 -0
  75. package/templates/fate/cloudflare/server/src/fate/commentPermissions.ts +17 -0
  76. package/templates/fate/cloudflare/server/src/fate/context.ts +15 -0
  77. package/templates/fate/cloudflare/server/src/fate/live.ts +7 -0
  78. package/templates/fate/cloudflare/server/src/fate/server.ts +411 -0
  79. package/templates/fate/cloudflare/server/src/fate/views.ts +164 -0
  80. package/templates/fate/cloudflare/server/src/index.ts +80 -0
  81. package/templates/fate/cloudflare/server/src/lib/auth.ts +66 -0
  82. package/templates/fate/cloudflare/server/src/lib/origins.ts +17 -0
  83. package/templates/fate/cloudflare/server/src/router.ts +2 -0
  84. package/templates/fate/cloudflare/server/src/user/SessionUser.tsx +17 -0
  85. package/templates/fate/cloudflare/server/tsconfig.json +10 -0
  86. package/templates/fate/cloudflare/server/wrangler.jsonc +29 -0
  87. package/templates/fate/cloudflare/tsconfig.json +33 -0
  88. package/templates/fate/cloudflare/vite.config.ts +72 -0
  89. package/templates/fate/drizzle/README.md +2 -8
  90. package/templates/fate/drizzle/client/pages/layout.tsx +2 -2
  91. package/templates/fate/drizzle/client/src/ui/PostCard.tsx +38 -15
  92. package/templates/fate/drizzle/pnpm-workspace.yaml +9 -3
  93. package/templates/fate/drizzle/server/vite.config.ts +1 -1
  94. package/templates/fate/graphql/.github/workflows/push.yml +27 -0
  95. package/templates/fate/graphql/.vite-hooks/pre-commit +1 -0
  96. package/templates/fate/graphql/.vscode/extensions.json +12 -0
  97. package/templates/fate/graphql/.vscode/settings.json +25 -0
  98. package/templates/fate/graphql/AGENTS.md +55 -0
  99. package/templates/fate/graphql/README.md +121 -0
  100. package/templates/fate/graphql/_gitignore +23 -0
  101. package/templates/fate/graphql/client/package.json +57 -0
  102. package/templates/fate/graphql/client/pages/index.tsx +96 -0
  103. package/templates/fate/graphql/client/pages/layout.tsx +83 -0
  104. package/templates/fate/graphql/client/pages/login.tsx +107 -0
  105. package/templates/fate/graphql/client/pages/post/[id].tsx +22 -0
  106. package/templates/fate/graphql/client/pages/search.tsx +99 -0
  107. package/templates/fate/graphql/client/public/icon.svg +1 -0
  108. package/templates/fate/graphql/client/src/App.css +153 -0
  109. package/templates/fate/graphql/client/src/lib/AvailableLanguages.tsx +5 -0
  110. package/templates/fate/graphql/client/src/lib/cx.tsx +6 -0
  111. package/templates/fate/graphql/client/src/lib/env.tsx +5 -0
  112. package/templates/fate/graphql/client/src/ui/Badge.tsx +31 -0
  113. package/templates/fate/graphql/client/src/ui/Button.tsx +108 -0
  114. package/templates/fate/graphql/client/src/ui/Card.tsx +32 -0
  115. package/templates/fate/graphql/client/src/ui/CommentCard.tsx +75 -0
  116. package/templates/fate/graphql/client/src/ui/Error.tsx +19 -0
  117. package/templates/fate/graphql/client/src/ui/H2.tsx +15 -0
  118. package/templates/fate/graphql/client/src/ui/H3.tsx +20 -0
  119. package/templates/fate/graphql/client/src/ui/Header.tsx +98 -0
  120. package/templates/fate/graphql/client/src/ui/Input.tsx +17 -0
  121. package/templates/fate/graphql/client/src/ui/Link.tsx +11 -0
  122. package/templates/fate/graphql/client/src/ui/LocaleSwitcher.tsx +52 -0
  123. package/templates/fate/graphql/client/src/ui/PostCard.tsx +303 -0
  124. package/templates/fate/graphql/client/src/ui/Section.tsx +23 -0
  125. package/templates/fate/graphql/client/src/ui/UserCard.tsx +131 -0
  126. package/templates/fate/graphql/client/src/user/AuthClient.tsx +6 -0
  127. package/templates/fate/graphql/client/translations/de_DE.json +689 -0
  128. package/templates/fate/graphql/client/translations/ja_JP.json +689 -0
  129. package/templates/fate/graphql/client/vite.config.ts +40 -0
  130. package/templates/fate/graphql/client/void.json +29 -0
  131. package/templates/fate/graphql/docker-compose.yml +11 -0
  132. package/templates/fate/graphql/package.json +47 -0
  133. package/templates/fate/graphql/pnpm-workspace.yaml +45 -0
  134. package/templates/fate/graphql/server/.env.example +5 -0
  135. package/templates/fate/graphql/server/README.md +27 -0
  136. package/templates/fate/graphql/server/package.json +64 -0
  137. package/templates/fate/graphql/server/prisma.config.ts +17 -0
  138. package/templates/fate/graphql/server/scripts/generate-graphql.tsx +40 -0
  139. package/templates/fate/graphql/server/src/__tests__/index.test.tsx +5 -0
  140. package/templates/fate/graphql/server/src/app.tsx +47 -0
  141. package/templates/fate/graphql/server/src/graphql/builder.tsx +121 -0
  142. package/templates/fate/graphql/server/src/graphql/context.tsx +22 -0
  143. package/templates/fate/graphql/server/src/graphql/fate.ts +85 -0
  144. package/templates/fate/graphql/server/src/graphql/lib/authDirectives.tsx +93 -0
  145. package/templates/fate/graphql/server/src/graphql/lib/decodeGlobalID.tsx +12 -0
  146. package/templates/fate/graphql/server/src/graphql/lib/decodeIDOrThrow.tsx +10 -0
  147. package/templates/fate/graphql/server/src/graphql/lib/encodeGlobalID.tsx +5 -0
  148. package/templates/fate/graphql/server/src/graphql/live.tsx +241 -0
  149. package/templates/fate/graphql/server/src/graphql/mutations.tsx +265 -0
  150. package/templates/fate/graphql/server/src/graphql/nodes/Category.tsx +35 -0
  151. package/templates/fate/graphql/server/src/graphql/nodes/Comment.tsx +46 -0
  152. package/templates/fate/graphql/server/src/graphql/nodes/Event.tsx +71 -0
  153. package/templates/fate/graphql/server/src/graphql/nodes/Post.tsx +45 -0
  154. package/templates/fate/graphql/server/src/graphql/nodes/Tag.tsx +11 -0
  155. package/templates/fate/graphql/server/src/graphql/nodes/User.tsx +28 -0
  156. package/templates/fate/graphql/server/src/graphql/schema.tsx +16 -0
  157. package/templates/fate/graphql/server/src/graphql/views.ts +193 -0
  158. package/templates/fate/graphql/server/src/index.tsx +45 -0
  159. package/templates/fate/graphql/server/src/lib/auth.tsx +31 -0
  160. package/templates/fate/graphql/server/src/lib/env.tsx +3 -0
  161. package/templates/fate/graphql/server/src/lib/origins.ts +18 -0
  162. package/templates/fate/graphql/server/src/prisma/prisma.tsx +9 -0
  163. package/templates/fate/graphql/server/src/prisma/schema.prisma +181 -0
  164. package/templates/fate/graphql/server/src/prisma/seed.tsx +161 -0
  165. package/templates/fate/graphql/server/src/user/SessionUser.tsx +17 -0
  166. package/templates/fate/graphql/server/src/user/isAdmin.tsx +3 -0
  167. package/templates/fate/graphql/server/tsconfig.json +33 -0
  168. package/templates/fate/graphql/server/vite.config.ts +55 -0
  169. package/templates/fate/graphql/tsconfig.json +33 -0
  170. package/templates/fate/graphql/vite.config.ts +80 -0
  171. package/templates/fate/graphql-client/.env.example +3 -0
  172. package/templates/fate/graphql-client/AGENTS.md +3 -0
  173. package/templates/fate/graphql-client/README.md +69 -0
  174. package/templates/fate/graphql-client/_gitignore +14 -0
  175. package/templates/fate/graphql-client/package.json +62 -0
  176. package/templates/fate/graphql-client/pages/index.tsx +69 -0
  177. package/templates/fate/graphql-client/pages/layout.tsx +41 -0
  178. package/templates/fate/graphql-client/pnpm-workspace.yaml +38 -0
  179. package/templates/fate/graphql-client/public/icon.svg +8 -0
  180. package/templates/fate/graphql-client/src/App.css +28 -0
  181. package/templates/fate/graphql-client/src/fate/graphql.ts +52 -0
  182. package/templates/fate/graphql-client/tsconfig.json +30 -0
  183. package/templates/fate/graphql-client/vite.config.ts +87 -0
  184. package/templates/fate/graphql-client/void.json +29 -0
  185. package/templates/fate/http/README.md +2 -8
  186. package/templates/fate/http/client/pages/layout.tsx +2 -2
  187. package/templates/fate/http/client/src/ui/PostCard.tsx +37 -15
  188. package/templates/fate/http/pnpm-workspace.yaml +9 -3
  189. package/templates/fate/http/server/vite.config.ts +1 -1
  190. package/templates/fate/prisma/README.md +2 -8
  191. package/templates/fate/prisma/client/pages/layout.tsx +2 -2
  192. package/templates/fate/prisma/client/src/ui/PostCard.tsx +37 -15
  193. package/templates/fate/prisma/pnpm-workspace.yaml +9 -3
  194. package/templates/fate/prisma/server/vite.config.ts +1 -1
  195. package/templates/fate/prisma/vite.config.ts +1 -1
  196. package/templates/fate/void/package.json +10 -2
  197. package/templates/fate/void/pnpm-workspace.yaml +12 -4
  198. package/templates/fate/void/src/ui/CategoryCard.tsx +3 -7
  199. package/templates/fate/void/src/ui/PostCard.tsx +38 -15
  200. package/templates/fate/void/vite.config.ts +5 -0
  201. package/templates/fate/drizzle/public/fate-logo-dark.svg +0 -4
  202. package/templates/fate/drizzle/public/fate-logo.svg +0 -4
  203. package/templates/fate/http/public/fate-logo-dark.svg +0 -4
  204. package/templates/fate/http/public/fate-logo.svg +0 -4
  205. package/templates/fate/prisma/public/fate-logo-dark.svg +0 -4
  206. package/templates/fate/prisma/public/fate-logo.svg +0 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fate",
3
- "version": "1.0.4",
3
+ "version": "1.3.0",
4
4
  "description": "Create a new fate app.",
5
5
  "homepage": "https://github.com/nkzw-tech/fate",
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "type": "module",
24
24
  "dependencies": {
25
- "@clack/prompts": "^1.4.0"
25
+ "@clack/prompts": "^1.5.0"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=24.0.0"
@@ -3,6 +3,9 @@
3
3
  This directory is the canonical source for fate project scaffolds.
4
4
 
5
5
  - `void/` contains the Void pages router + Drizzle template.
6
+ - `cloudflare/` contains the Cloudflare Workers + D1 + Drizzle template.
6
7
  - `drizzle/` contains the tRPC + Drizzle template.
8
+ - `graphql/` contains the GraphQL + Prisma template.
9
+ - `graphql-client/` contains the existing GraphQL server template.
7
10
  - `http/` contains the native HTTP + Drizzle template.
8
11
  - `prisma/` contains the tRPC + Prisma template.
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@app/client",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "description": "fate is a modern data client for Vue.",
6
+ "author": "Christoph Nakazawa <christoph.pojer@gmail.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git://github.com/nkzw-tech/fate.git"
10
+ },
11
+ "type": "module",
12
+ "scripts": {
13
+ "build": "vp build",
14
+ "dev": "vp dev",
15
+ "dev:setup": "vp run prepare:void && vp run fate:generate",
16
+ "fate:generate": "vp exec fate generate",
17
+ "prepare:void": "void prepare"
18
+ },
19
+ "dependencies": {
20
+ "@nkzw/core": "^1.3.1",
21
+ "@nkzw/define-env": "^1.1.0",
22
+ "@trpc/client": "^11.17.0",
23
+ "@void/vue": "^0.9.2",
24
+ "class-variance-authority": "^0.7.1",
25
+ "clsx": "^2.1.1",
26
+ "dotenv": "^17.4.2",
27
+ "graphql-sse": "^2.6.0",
28
+ "lucide-vue-next": "^0.561.0",
29
+ "tailwind-merge": "^3.5.0",
30
+ "void": "^0.9.2",
31
+ "vue": "^3.5.24",
32
+ "vue-fate": "latest"
33
+ },
34
+ "devDependencies": {
35
+ "@nkzw/eslint-plugin": "^2.0.0",
36
+ "@nkzw/oxlint-config": "^1.2.1",
37
+ "@swc/core": "^1.15.33",
38
+ "@tailwindcss/vite": "^4.2.4",
39
+ "@types/node": "^25.6.0",
40
+ "@typescript/native-preview": "7.0.0-dev.20260505.1",
41
+ "eslint-plugin-no-only-tests": "^3.4.0",
42
+ "eslint-plugin-perfectionist": "^5.9.0",
43
+ "tailwindcss": "^4.2.4",
44
+ "ts-node": "^10.9.2",
45
+ "vite": "catalog:",
46
+ "vite-plus": "catalog:",
47
+ "vitest": "catalog:"
48
+ },
49
+ "browserslist": [
50
+ ">0.2%",
51
+ "not dead",
52
+ "not op_mini all"
53
+ ]
54
+ }
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ import { useListView, useRequest } from 'vue-fate';
3
+ import { PostView, UserView } from '../src/fateViews.ts';
4
+ import Button from '../src/ui/Button.vue';
5
+ import Card from '../src/ui/Card.vue';
6
+ import PostCard from '../src/ui/PostCard.vue';
7
+ import Section from '../src/ui/Section.vue';
8
+ import UserCard from '../src/ui/UserCard.vue';
9
+
10
+ const PostConnectionView = {
11
+ args: { first: 10 },
12
+ items: {
13
+ node: PostView,
14
+ },
15
+ pagination: {
16
+ hasNext: true,
17
+ },
18
+ };
19
+
20
+ const request = useRequest({
21
+ posts: { list: PostConnectionView },
22
+ viewer: { view: UserView },
23
+ });
24
+ const { posts: postsRef, viewer } = await request.ready();
25
+ const [posts, loadNext] = useListView(PostConnectionView, postsRef);
26
+ </script>
27
+
28
+ <template>
29
+ <Section :gap="32">
30
+ <div class="grid gap-8 lg:grid-cols-[1.6fr_1fr] lg:items-stretch">
31
+ <Card class="border border-white/20 bg-linear-to-r from-blue-500 to-sky-500 text-white">
32
+ <div class="space-y-3">
33
+ <p class="text-xs font-semibold tracking-widest uppercase">
34
+ <span class="lowercase italic">fate</span> demo
35
+ </p>
36
+ <h1 class="text-3xl leading-tight font-semibold text-balance lg:text-4xl">
37
+ fate is a modern data client for Vue inspired by Relay and GraphQL.
38
+ </h1>
39
+ <p class="text-sm text-white/80 lg:text-base">
40
+ This template uses Vue-native fate composables, normalized caching, data masking, and
41
+ type-safe data fetching.
42
+ </p>
43
+ </div>
44
+ </Card>
45
+ <UserCard v-if="viewer" :viewer="viewer" />
46
+ </div>
47
+ <div v-if="posts.length" class="flex flex-col gap-4">
48
+ <h2 class="pl-5 text-2xl font-semibold tracking-tight">Latest posts</h2>
49
+ <div class="flex flex-col gap-4">
50
+ <PostCard v-for="{ node } in posts" :key="node.id" :post="node" />
51
+ </div>
52
+ <div v-if="loadNext" class="flex justify-center">
53
+ <Button :action="loadNext" variant="ghost">Load more posts</Button>
54
+ </div>
55
+ </div>
56
+ </Section>
57
+ </template>
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ import '../src/App.css';
3
+ import { computed } from 'vue';
4
+ import { FateClient } from 'vue-fate';
5
+ import { createFateClient } from 'vue-fate/client';
6
+ /* __CLIENT_IMPORTS__ */
7
+ import ErrorBoundary from '../src/ui/ErrorBoundary.vue';
8
+ import Header from '../src/ui/Header.vue';
9
+ import Thinking from '../src/ui/Thinking.vue';
10
+
11
+ const fate = computed(() => createFateClient(__CREATE_FATE_OPTIONS__));
12
+ </script>
13
+
14
+ <template>
15
+ <div class="min-h-screen bg-background text-foreground">
16
+ <div
17
+ class="min-h-screen bg-[radial-gradient(circle_at_20%_20%,rgba(59,130,246,0.08),transparent_35%),radial-gradient(circle_at_80%_0,rgba(99,102,241,0.08),transparent_28%)]"
18
+ >
19
+ <Header />
20
+ <FateClient :client="fate">
21
+ <ErrorBoundary>
22
+ <Suspense timeout="0">
23
+ <slot />
24
+ <template #fallback>
25
+ <Thinking />
26
+ </template>
27
+ </Suspense>
28
+ </ErrorBoundary>
29
+ </FateClient>
30
+ </div>
31
+ </div>
32
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ import { useParams } from '@void/vue';
3
+ import { useRequest } from 'vue-fate';
4
+ import { PostView } from '../../src/fateViews.ts';
5
+ import PostCard from '../../src/ui/PostCard.vue';
6
+ import Section from '../../src/ui/Section.vue';
7
+
8
+ const params = useParams<{ id: string }>();
9
+ const id = params.id;
10
+
11
+ if (!id) {
12
+ throw new Error('fate: Post ID is required.');
13
+ }
14
+
15
+ const { post } = await useRequest({
16
+ post: { id, view: PostView },
17
+ }).ready();
18
+ </script>
19
+
20
+ <template>
21
+ <Section>
22
+ <PostCard :post="post" />
23
+ </Section>
24
+ </template>
@@ -0,0 +1,63 @@
1
+ @import 'tailwindcss';
2
+
3
+ body {
4
+ font-family:
5
+ system-ui,
6
+ -apple-system,
7
+ BlinkMacSystemFont,
8
+ 'Segoe UI',
9
+ 'Open Sans',
10
+ 'Helvetica Neue',
11
+ sans-serif;
12
+
13
+ background-color: hsl(var(--background));
14
+ color: hsl(var(--foreground));
15
+ }
16
+
17
+ @theme {
18
+ --color-background: hsl(var(--background));
19
+ --color-foreground: hsl(var(--foreground));
20
+ --color-border: hsl(var(--border));
21
+ --color-card: hsl(var(--card));
22
+ --color-card-foreground: hsl(var(--card-foreground));
23
+ --color-muted-foreground: hsl(var(--muted-foreground));
24
+ --color-primary: hsl(var(--primary));
25
+ --color-primary-foreground: hsl(var(--primary-foreground));
26
+ --color-secondary: hsl(var(--secondary));
27
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
28
+ }
29
+
30
+ @layer base {
31
+ :root {
32
+ --background: 0 0% 100%;
33
+ --foreground: 0 0% 3.9%;
34
+ --card: 0 0% 100%;
35
+ --card-foreground: 0 0% 3.9%;
36
+ --border: 0 0% 89.8%;
37
+ --muted-foreground: 0 0% 40%;
38
+ --primary: 0 0% 9%;
39
+ --primary-foreground: 0 0% 98%;
40
+ --secondary: 0 0% 96.1%;
41
+ --secondary-foreground: 0 0% 9%;
42
+ }
43
+
44
+ @media (prefers-color-scheme: dark) {
45
+ :root {
46
+ --background: 0 0% 3.9%;
47
+ --foreground: 0 0% 98%;
48
+ --card: 0 0% 3.9%;
49
+ --card-foreground: 0 0% 98%;
50
+ --border: 0 0% 14.9%;
51
+ --muted-foreground: 0 0% 63.9%;
52
+ --primary: 0 0% 98%;
53
+ --primary-foreground: 0 0% 9%;
54
+ --secondary: 0 0% 14.9%;
55
+ --secondary-foreground: 0 0% 98%;
56
+ }
57
+ }
58
+ }
59
+
60
+ .squircle {
61
+ border-radius: 32px;
62
+ corner-shape: squircle;
63
+ }
@@ -0,0 +1,14 @@
1
+ import type { Post, User } from '__FATE_TYPE_MODULE__';
2
+ import { view } from 'vue-fate';
3
+
4
+ export const UserView = view<User>()({
5
+ id: true,
6
+ name: true,
7
+ username: true,
8
+ });
9
+
10
+ export const PostView = view<Post>()({
11
+ author: UserView,
12
+ id: true,
13
+ title: true,
14
+ });
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export default function cx(...inputs: Array<ClassValue>) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,3 @@
1
+ import defineEnv from '@nkzw/define-env';
2
+
3
+ export default defineEnv(__ENV_KEYS__, __ENV_VALUES__);
@@ -0,0 +1,67 @@
1
+ <script setup lang="ts">
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { computed, ref } from 'vue';
4
+ import cx from '../lib/cx.ts';
5
+
6
+ const buttonVariants = cva(
7
+ 'squircle inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50',
8
+ {
9
+ defaultVariants: {
10
+ size: 'default',
11
+ variant: 'default',
12
+ },
13
+ variants: {
14
+ size: {
15
+ default: 'h-10 px-3 py-2',
16
+ sm: 'h-9 px-2',
17
+ },
18
+ variant: {
19
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
20
+ ghost: 'hover:bg-secondary hover:text-secondary-foreground',
21
+ outline: 'border border-border bg-background hover:bg-secondary',
22
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
23
+ },
24
+ },
25
+ },
26
+ );
27
+
28
+ type ButtonVariants = VariantProps<typeof buttonVariants>;
29
+
30
+ const props = withDefaults(
31
+ defineProps<{
32
+ action?: () => Promise<unknown> | unknown;
33
+ class?: string;
34
+ disabled?: boolean;
35
+ size?: ButtonVariants['size'];
36
+ type?: 'button' | 'submit';
37
+ variant?: ButtonVariants['variant'];
38
+ }>(),
39
+ {
40
+ type: 'button',
41
+ },
42
+ );
43
+
44
+ const isPending = ref(false);
45
+ const classes = computed(() =>
46
+ cx(buttonVariants({ size: props.size, variant: props.variant }), props.class),
47
+ );
48
+
49
+ const onClick = async () => {
50
+ if (!props.action || isPending.value || props.disabled) {
51
+ return;
52
+ }
53
+
54
+ isPending.value = true;
55
+ try {
56
+ await props.action();
57
+ } finally {
58
+ isPending.value = false;
59
+ }
60
+ };
61
+ </script>
62
+
63
+ <template>
64
+ <button :class="classes" :disabled="disabled || isPending" :type="type" @click="onClick">
65
+ <slot />
66
+ </button>
67
+ </template>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import cx from '../lib/cx.ts';
4
+
5
+ const props = defineProps<{ class?: string }>();
6
+
7
+ const classes = computed(() =>
8
+ cx('rounded-lg border border-border bg-card p-5 text-card-foreground shadow-sm', props.class),
9
+ );
10
+ </script>
11
+
12
+ <template>
13
+ <div :class="classes">
14
+ <slot />
15
+ </div>
16
+ </template>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ error?: unknown }>();
3
+ </script>
4
+
5
+ <template>
6
+ <div class="text-sm text-red-600 dark:text-red-400">
7
+ {{ error instanceof Error ? error.message : 'Something went wrong.' }}
8
+ </div>
9
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ import { onErrorCaptured, ref } from 'vue';
3
+ import Card from './Card.vue';
4
+ import Error from './Error.vue';
5
+ import Section from './Section.vue';
6
+
7
+ const error = ref<unknown>(null);
8
+
9
+ onErrorCaptured((capturedError) => {
10
+ error.value = capturedError;
11
+ return false;
12
+ });
13
+ </script>
14
+
15
+ <template>
16
+ <Section v-if="error">
17
+ <Card>
18
+ <Error :error="error" />
19
+ </Card>
20
+ </Section>
21
+ <slot v-else />
22
+ </template>
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ import { Link } from '@void/vue';
3
+ import { Search } from 'lucide-vue-next';
4
+ import Button from './Button.vue';
5
+ </script>
6
+
7
+ <template>
8
+ <header
9
+ class="sticky top-0 z-50 border-b border-white/60 bg-white/60 backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/70"
10
+ >
11
+ <div class="container mx-auto flex h-16 max-w-5xl items-center justify-between gap-4 px-6">
12
+ <Link class="flex items-center gap-3" href="/">
13
+ <div
14
+ class="squircle flex h-10 w-10 items-center justify-center bg-linear-to-br from-gray-100 to-gray-200 text-gray-500 shadow-lg shadow-gray-500/20"
15
+ >
16
+ f
17
+ </div>
18
+ <div>
19
+ <span
20
+ class="bg-linear-to-r from-gray-500 to-gray-900 bg-clip-text text-xl font-semibold text-transparent dark:from-gray-200 dark:to-white"
21
+ >
22
+ <span class="italic">fate</span>
23
+ </span>
24
+ <p class="hidden text-xs text-muted-foreground sm:block">A modern data client for Vue.</p>
25
+ </div>
26
+ </Link>
27
+ <Button size="sm" variant="ghost">
28
+ <Link class="inline-flex items-center gap-2" href="/">
29
+ <Search class="h-4 w-4" />
30
+ <span class="hidden sm:inline">Home</span>
31
+ </Link>
32
+ </Button>
33
+ </div>
34
+ </header>
35
+ </template>
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ import { Link as VoidLink } from '@void/vue';
3
+
4
+ defineProps<{
5
+ class?: string;
6
+ href?: string;
7
+ to?: string;
8
+ }>();
9
+ </script>
10
+
11
+ <template>
12
+ <VoidLink :class="$props.class" :href="href ?? to ?? '/'">
13
+ <slot />
14
+ </VoidLink>
15
+ </template>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ import type { ViewRef } from 'vue-fate';
3
+ import { useView } from 'vue-fate';
4
+ import { PostView, UserView } from '../fateViews.ts';
5
+ import Card from './Card.vue';
6
+ import Link from './Link.vue';
7
+
8
+ const props = defineProps<{
9
+ post: ViewRef<'Post'>;
10
+ }>();
11
+
12
+ const post = useView(PostView, () => props.post);
13
+ const author = useView(UserView, () => post.value?.author ?? null);
14
+ </script>
15
+
16
+ <template>
17
+ <Card v-if="post">
18
+ <article class="space-y-3">
19
+ <div>
20
+ <Link class="text-lg font-semibold hover:underline" :href="`/post/${post.id}`">
21
+ {{ post.title }}
22
+ </Link>
23
+ <p v-if="author" class="mt-1 text-sm text-muted-foreground">
24
+ By {{ author.name ?? author.username ?? author.id }}
25
+ </p>
26
+ </div>
27
+ </article>
28
+ </Card>
29
+ </template>
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import cx from '../lib/cx.ts';
4
+
5
+ const props = defineProps<{
6
+ class?: string;
7
+ gap?: number;
8
+ }>();
9
+
10
+ const classes = computed(() =>
11
+ cx('container mx-auto flex max-w-5xl flex-col px-4 py-6 lg:px-8 lg:py-10', props.class),
12
+ );
13
+ </script>
14
+
15
+ <template>
16
+ <section :class="classes" :style="{ gap: `${gap ?? 16}px` }">
17
+ <slot />
18
+ </section>
19
+ </template>
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ import Section from './Section.vue';
3
+ </script>
4
+
5
+ <template>
6
+ <Section>
7
+ <div class="flex animate-pulse items-center justify-center py-12 text-gray-500 italic">
8
+ Thinking...
9
+ </div>
10
+ </Section>
11
+ </template>
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import type { ViewRef } from 'vue-fate';
3
+ import { useView } from 'vue-fate';
4
+ import { UserView } from '../fateViews.ts';
5
+ import Card from './Card.vue';
6
+
7
+ const props = defineProps<{
8
+ viewer: ViewRef<'User'>;
9
+ }>();
10
+
11
+ const viewer = useView(UserView, () => props.viewer);
12
+ </script>
13
+
14
+ <template>
15
+ <Card v-if="viewer">
16
+ <div class="space-y-2">
17
+ <h2 class="text-sm font-medium text-muted-foreground">Viewer</h2>
18
+ <p class="text-lg font-semibold">
19
+ {{ viewer.name ?? viewer.username ?? viewer.id }}
20
+ </p>
21
+ </div>
22
+ </Card>
23
+ </template>
@@ -0,0 +1,6 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue';
3
+
4
+ const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>;
5
+ export default component;
6
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "__TSCONFIG_EXTENDS__",
3
+ "compilerOptions": {
4
+ "allowImportingTsExtensions": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "incremental": true,
8
+ "isolatedModules": true,
9
+ "lib": ["dom", "dom.iterable", "esnext"],
10
+ "module": "ESNext",
11
+ "moduleResolution": "Bundler",
12
+ "noEmit": true,
13
+ "noImplicitOverride": true,
14
+ "noUnusedLocals": true,
15
+ "resolveJsonModule": true,
16
+ "skipLibCheck": true,
17
+ "strict": true,
18
+ "target": "ES2022",
19
+ "types": ["vite/client", "void/env"]
20
+ },
21
+ "include": "__TSCONFIG_INCLUDE__"
22
+ }
@@ -0,0 +1,58 @@
1
+ import { join } from 'node:path';
2
+ import nkzw from '@nkzw/oxlint-config';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import { voidVue } from '@void/vue/plugin';
5
+ import dotenv from 'dotenv';
6
+ import { defineConfig, lazyPlugins } from 'vite-plus';
7
+ import { voidPlugin } from 'void';
8
+ import { fate } from 'vue-fate/vite';
9
+
10
+ const root = process.cwd();
11
+ __DOTENV_CONFIG__;
12
+
13
+ export default defineConfig({
14
+ /* __BUILD_CONFIG__ */
15
+ optimizeDeps: {
16
+ exclude: ['@nkzw/fate/client', 'vue-fate/client'],
17
+ },
18
+ fmt: {
19
+ experimentalSortImports: {
20
+ newlinesBetween: false,
21
+ },
22
+ experimentalSortPackageJson: {
23
+ sortScripts: true,
24
+ },
25
+ experimentalTailwindcss: {
26
+ stylesheet: 'src/App.css',
27
+ },
28
+ ignorePatterns: __FMT_IGNORE_PATTERNS__,
29
+ singleQuote: true,
30
+ },
31
+ lint: {
32
+ extends: [nkzw],
33
+ ignorePatterns: __LINT_IGNORE_PATTERNS__,
34
+ options: { typeAware: true, typeCheck: true },
35
+ rules: {
36
+ '@typescript-eslint/no-explicit-any': 'off',
37
+ },
38
+ },
39
+ plugins: [
40
+ ...(lazyPlugins(() => [tailwindcss(), voidPlugin(), voidVue()]) ?? []),
41
+ fate({
42
+ module: '__FATE_MODULE__',
43
+ /* __FATE_TRANSPORT__ */
44
+ }),
45
+ ],
46
+ run: {
47
+ tasks: {
48
+ 'test:all': {
49
+ command: 'vp check && vp test',
50
+ },
51
+ },
52
+ },
53
+ server: { port: 6001 },
54
+ ssr: { noExternal: ['@nkzw/fate', 'vue-fate'] },
55
+ staged: {
56
+ '*': 'vp check --fix',
57
+ },
58
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "pages": {
3
+ "router": true
4
+ }
5
+ }
@@ -0,0 +1,27 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ node-version: [24]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: voidzero-dev/setup-vp@v1
20
+ with:
21
+ node-version: ${{ matrix.node-version }}
22
+ cache: true
23
+ run-install: true
24
+ - run: |
25
+ vp run dev:setup
26
+ vp run test:all
27
+ vp run build