create-fate 1.0.3 → 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 (208) 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/pages/login.tsx +3 -3
  198. package/templates/fate/void/pnpm-workspace.yaml +12 -4
  199. package/templates/fate/void/src/ui/Button.tsx +42 -26
  200. package/templates/fate/void/src/ui/CategoryCard.tsx +3 -7
  201. package/templates/fate/void/src/ui/PostCard.tsx +38 -15
  202. package/templates/fate/void/vite.config.ts +15 -14
  203. package/templates/fate/drizzle/public/fate-logo-dark.svg +0 -4
  204. package/templates/fate/drizzle/public/fate-logo.svg +0 -4
  205. package/templates/fate/http/public/fate-logo-dark.svg +0 -4
  206. package/templates/fate/http/public/fate-logo.svg +0 -4
  207. package/templates/fate/prisma/public/fate-logo-dark.svg +0 -4
  208. package/templates/fate/prisma/public/fate-logo.svg +0 -4
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env NODE_ENV=development node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm --env-file .env
2
+ import { styleText } from 'node:util';
3
+ import randomEntry from '@nkzw/core/randomEntry.js';
4
+ import { categories, comments, events, posts, tags, users } from '../../../seedData.ts';
5
+ import { auth } from '../lib/auth.tsx';
6
+ import prisma from './prisma.tsx';
7
+
8
+ console.log(styleText('bold', '› Seeding database...'));
9
+
10
+ try {
11
+ console.log(styleText('bold', `Creating users`));
12
+
13
+ for (const data of users) {
14
+ const { user } = await auth.api.createUser({
15
+ body: data,
16
+ });
17
+
18
+ console.log(` Created user ${styleText('blue', user.name)}.`);
19
+ }
20
+
21
+ const seededUsers = await prisma.user.findMany();
22
+ const usersByEmail = new Map(seededUsers.map((user) => [user.email, user]));
23
+
24
+ console.log(styleText('bold', `Creating categories and tags`));
25
+
26
+ const createdCategories = await Promise.all(
27
+ categories.map((category) =>
28
+ prisma.category.create({
29
+ data: category,
30
+ }),
31
+ ),
32
+ );
33
+ const createdTags = await Promise.all(
34
+ tags.map((tag) =>
35
+ prisma.tag.create({
36
+ data: tag,
37
+ }),
38
+ ),
39
+ );
40
+
41
+ const categoriesByName = new Map(createdCategories.map((category) => [category.name, category]));
42
+ const tagsByName = new Map(createdTags.map((tag) => [tag.name, tag]));
43
+
44
+ console.log(styleText('bold', `Seeding posts and comments`));
45
+
46
+ const createdPosts = await Promise.all(
47
+ posts.map((post) => {
48
+ const author = usersByEmail.get(post.authorEmail);
49
+
50
+ if (!author) {
51
+ throw new Error(`Missing seeded user for ${post.authorEmail}.`);
52
+ }
53
+
54
+ const category = categoriesByName.get(post.category);
55
+ const tagConnections = post.tags
56
+ .map((name) => tagsByName.get(name))
57
+ .filter(Boolean)
58
+ .map((tag) => ({ id: tag!.id }));
59
+
60
+ return prisma.post.create({
61
+ data: {
62
+ authorId: author.id,
63
+ categoryId: category?.id,
64
+ content: post.content,
65
+ likes: post.likes,
66
+ tags: tagConnections.length
67
+ ? {
68
+ connect: tagConnections,
69
+ }
70
+ : undefined,
71
+ title: post.title,
72
+ },
73
+ });
74
+ }),
75
+ );
76
+
77
+ let index = 0;
78
+ for (const comment of comments) {
79
+ const post = createdPosts[index % createdPosts.length];
80
+ const author = randomEntry(seededUsers);
81
+
82
+ await prisma.comment.create({
83
+ data: {
84
+ authorId: author?.id,
85
+ content: comment,
86
+ postId: post.id,
87
+ },
88
+ });
89
+
90
+ index++;
91
+ }
92
+
93
+ console.log(
94
+ styleText(
95
+ ['green', 'bold'],
96
+ `✓ Created ${createdPosts.length} posts and ${comments.length} comments.`,
97
+ ),
98
+ );
99
+
100
+ console.log(styleText('bold', `Creating community events`));
101
+
102
+ const createdEvents = await Promise.all(
103
+ events.map((event) => {
104
+ const host = usersByEmail.get(event.hostEmail);
105
+
106
+ if (!host) {
107
+ throw new Error(`Missing seeded user for ${event.hostEmail}.`);
108
+ }
109
+
110
+ const attendees = event.attendees
111
+ .map((attendee) => {
112
+ const attendeeUser = usersByEmail.get(attendee.userEmail);
113
+
114
+ if (!attendeeUser) {
115
+ throw new Error(`Missing seeded user for ${attendee.userEmail}.`);
116
+ }
117
+
118
+ return {
119
+ notes: attendee.notes,
120
+ status: attendee.status,
121
+ userId: attendeeUser.id,
122
+ };
123
+ })
124
+ .filter(Boolean);
125
+
126
+ return prisma.event.create({
127
+ data: {
128
+ attendees: attendees.length
129
+ ? {
130
+ create: attendees,
131
+ }
132
+ : undefined,
133
+ capacity: event.capacity,
134
+ description: event.description,
135
+ endAt: event.endAt,
136
+ hostId: host.id,
137
+ livestreamUrl: event.livestreamUrl,
138
+ location: event.location,
139
+ name: event.name,
140
+ startAt: event.startAt,
141
+ topics: [...event.topics],
142
+ type: event.type,
143
+ },
144
+ });
145
+ }),
146
+ );
147
+
148
+ console.log(
149
+ styleText(
150
+ ['green', 'bold'],
151
+ `✓ Created ${createdEvents.length} events with ${events.reduce(
152
+ (total, event) => total + event.attendees.length,
153
+ 0,
154
+ )} attendee records.`,
155
+ ),
156
+ );
157
+
158
+ console.log(styleText(['green', 'bold'], '✓ Done.'));
159
+ } finally {
160
+ await prisma.$disconnect();
161
+ }
@@ -0,0 +1,17 @@
1
+ import { User } from '../prisma/prisma-client/client.ts';
2
+
3
+ export type SessionUser = Pick<User, 'role' | 'id' | 'username'>;
4
+
5
+ export function toSessionUser({
6
+ email,
7
+ id,
8
+ role,
9
+ username,
10
+ }: Readonly<{
11
+ email: string | null | undefined;
12
+ id: string;
13
+ role?: string | null | undefined;
14
+ username?: string | null | undefined;
15
+ }>): SessionUser {
16
+ return { id, role: role || '', username: username || email || '' };
17
+ }
@@ -0,0 +1,3 @@
1
+ export default function isAdmin({ role }: { role: string }): boolean {
2
+ return role.split(',').includes('admin');
3
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "allowImportingTsExtensions": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "incremental": true,
8
+ "isolatedModules": true,
9
+ "jsx": "react-jsx",
10
+ "lib": ["esnext"],
11
+ "module": "nodenext",
12
+ "moduleResolution": "nodenext",
13
+ "noEmit": true,
14
+ "noImplicitOverride": true,
15
+ "noUnusedLocals": true,
16
+ "resolveJsonModule": true,
17
+ "skipLibCheck": true,
18
+ "strict": true,
19
+ "target": "es2022"
20
+ },
21
+ "exclude": ["node_modules", "src/prisma/pothos-types.ts", "src/prisma/prisma-client"],
22
+ "include": ["**/*.ts", "**/*.tsx"],
23
+ "ts-node": {
24
+ "transpileOnly": true,
25
+ "transpiler": "ts-node/transpilers/swc",
26
+ "files": true,
27
+ "compilerOptions": {
28
+ "module": "esnext",
29
+ "isolatedModules": false,
30
+ "target": "es2022"
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,55 @@
1
+ import nkzw from '@nkzw/oxlint-config';
2
+ import { defineConfig } from 'vite-plus';
3
+
4
+ export default defineConfig({
5
+ fmt: {
6
+ experimentalSortImports: {
7
+ newlinesBetween: false,
8
+ },
9
+ experimentalSortPackageJson: {
10
+ sortScripts: true,
11
+ },
12
+ ignorePatterns: ['coverage/', 'dist/', 'pnpm-lock.yaml', 'src/graphql/schema.graphql'],
13
+ singleQuote: true,
14
+ },
15
+ lint: {
16
+ extends: [nkzw],
17
+ ignorePatterns: ['dist/', 'src/prisma/pothos-types.ts', 'src/prisma/prisma-client/**'],
18
+ options: { typeAware: true, typeCheck: true },
19
+ overrides: [
20
+ {
21
+ files: ['scripts/**/*.tsx', 'src/app.tsx', 'src/index.tsx', 'src/prisma/seed.tsx'],
22
+ rules: {
23
+ 'no-console': 'off',
24
+ },
25
+ },
26
+ ],
27
+ rules: {
28
+ 'import-x-js/no-extraneous-dependencies': [
29
+ 'error',
30
+ {
31
+ devDependencies: [
32
+ './prisma.config.ts',
33
+ './scripts/**/*.tsx',
34
+ './src/__tests__/**/*.tsx',
35
+ './vite.config.ts',
36
+ ],
37
+ },
38
+ ],
39
+ },
40
+ },
41
+ pack: {
42
+ entry: ['./src/app.tsx'],
43
+ outputOptions: { codeSplitting: false, entryFileNames: 'index.js' },
44
+ },
45
+ run: {
46
+ tasks: {
47
+ 'test:all': {
48
+ command: 'vp check && vp test',
49
+ },
50
+ },
51
+ },
52
+ staged: {
53
+ '*': 'vp check --fix',
54
+ },
55
+ });
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "allowImportingTsExtensions": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "incremental": true,
8
+ "isolatedModules": true,
9
+ "jsx": "react-jsx",
10
+ "lib": ["dom", "dom.iterable", "esnext"],
11
+ "module": "nodenext",
12
+ "moduleResolution": "nodenext",
13
+ "noEmit": true,
14
+ "noImplicitOverride": true,
15
+ "noUnusedLocals": true,
16
+ "resolveJsonModule": true,
17
+ "skipLibCheck": true,
18
+ "strict": true,
19
+ "target": "es2017",
20
+ "types": ["vite/client"]
21
+ },
22
+ "exclude": ["**/.void/**/*.ts", "node_modules"],
23
+ "include": ["**/.fate/*.ts", "**/*.ts", "**/*.tsx"],
24
+ "ts-node": {
25
+ "transpileOnly": true,
26
+ "transpiler": "ts-node/transpilers/swc",
27
+ "files": true,
28
+ "compilerOptions": {
29
+ "module": "esnext",
30
+ "isolatedModules": false
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,80 @@
1
+ import { join } from 'node:path';
2
+ import nkzw from '@nkzw/oxlint-config';
3
+ import dotenv from 'dotenv';
4
+ import { defineConfig } from 'vite-plus';
5
+
6
+ const root = process.cwd();
7
+
8
+ dotenv.config({
9
+ path: join(root, './server', '.env'),
10
+ quiet: true,
11
+ });
12
+
13
+ export default defineConfig({
14
+ fmt: {
15
+ experimentalSortImports: {
16
+ newlinesBetween: false,
17
+ },
18
+ experimentalSortPackageJson: {
19
+ sortScripts: true,
20
+ },
21
+ experimentalTailwindcss: {
22
+ stylesheet: 'client/src/App.css',
23
+ },
24
+ ignorePatterns: [
25
+ 'coverage/',
26
+ 'dist/',
27
+ '.fate/',
28
+ 'client/dist/',
29
+ 'client/src/translations/',
30
+ 'server/dist/',
31
+ 'server/src/graphql/schema.graphql',
32
+ 'pnpm-lock.yaml',
33
+ ],
34
+ singleQuote: true,
35
+ },
36
+ lint: {
37
+ extends: [nkzw],
38
+ ignorePatterns: [
39
+ 'coverage',
40
+ 'dist',
41
+ '.fate',
42
+ 'client/dist',
43
+ 'server/dist',
44
+ 'server/src/graphql/schemaImportMap.tsx',
45
+ 'server/src/prisma/pothos-types.ts',
46
+ 'server/src/prisma/prisma-client/**',
47
+ 'vite.config.ts.timestamp-*',
48
+ 'client/vite.config.ts.timestamp-*',
49
+ 'server/vite.config.ts.timestamp-*',
50
+ ],
51
+ options: { typeAware: true, typeCheck: true },
52
+ overrides: [
53
+ {
54
+ files: [
55
+ 'server/scripts/**/*.tsx',
56
+ 'server/src/app.tsx',
57
+ 'server/src/index.tsx',
58
+ 'server/src/prisma/seed.tsx',
59
+ '**/__tests__/**',
60
+ ],
61
+ rules: {
62
+ 'no-console': 'off',
63
+ },
64
+ },
65
+ ],
66
+ rules: {
67
+ '@typescript-eslint/no-explicit-any': 'off',
68
+ },
69
+ },
70
+ run: {
71
+ tasks: {
72
+ 'test:all': {
73
+ command: 'vp check && vp test',
74
+ },
75
+ },
76
+ },
77
+ staged: {
78
+ '*': 'vp check --fix',
79
+ },
80
+ });
@@ -0,0 +1,3 @@
1
+ VITE_GRAPHQL_URL="https://api.example.com/graphql"
2
+ VITE_GRAPHQL_LIVE_URL=""
3
+
@@ -0,0 +1,3 @@
1
+ Run `vp run test:all` to verify changes.
2
+
3
+ Run `vp check --fix` to fix formatting and lint errors.
@@ -0,0 +1,69 @@
1
+ # _fate_
2
+
3
+ **_fate_** is a modern data client for React inspired by [Relay](https://relay.dev/) and [GraphQL](https://graphql.org/). It combines view composition, normalized caching, data masking, Async React features, and type-safe data fetching.
4
+
5
+ ## Existing GraphQL Template
6
+
7
+ Use this template when you want to use _fate_ with an existing GraphQL server.
8
+
9
+ ```bash
10
+ vp create fate my-app --template graphql-client
11
+ ```
12
+
13
+ ## Connect Your Server
14
+
15
+ Set the GraphQL endpoint in `.env`:
16
+
17
+ ```bash
18
+ VITE_GRAPHQL_URL="https://api.example.com/graphql"
19
+ ```
20
+
21
+ If your server supports GraphQL SSE, set the live endpoint too:
22
+
23
+ ```bash
24
+ VITE_GRAPHQL_LIVE_URL="https://api.example.com/graphql/stream"
25
+ ```
26
+
27
+ Then edit `src/fate/graphql.ts`. This file is the mapping layer between your GraphQL schema and _fate_:
28
+
29
+ - `dataView(...)` describes the fields React components are allowed to select.
30
+ - `Root` describes the root GraphQL fields _fate_ can request.
31
+ - `fateGraphQL.roots` maps _fate_ root names to GraphQL field names.
32
+ - `fateGraphQL.mutations` maps _fate_ mutation names to GraphQL mutation fields.
33
+
34
+ The sample assumes your GraphQL server exposes:
35
+
36
+ - `viewer`
37
+ - `posts(first:, after:)`
38
+ - `node(id:)` or `nodes(ids:)`
39
+ - Relay-style connections with `edges`, `cursor`, `node`, and `pageInfo`
40
+ - Entity objects with stable `id` and `__typename`
41
+
42
+ Replace the sample `User` and `Post` views with your own schema types.
43
+
44
+ ## Development
45
+
46
+ Install dependencies:
47
+
48
+ ```bash
49
+ vp install
50
+ ```
51
+
52
+ Generate the _fate_ client:
53
+
54
+ ```bash
55
+ vp run fate:generate
56
+ ```
57
+
58
+ Start the app:
59
+
60
+ ```bash
61
+ vp run dev
62
+ ```
63
+
64
+ Common commands:
65
+
66
+ - `vp run dev` starts the client.
67
+ - `vp run fate:generate` refreshes _fate_ client support after changing `src/fate/graphql.ts`.
68
+ - `vp check --fix` formats, lints, and type-checks the project.
69
+ - `vp run test:all` verifies the project.
@@ -0,0 +1,14 @@
1
+ .fate/
2
+ **/.fate/
3
+ .void/
4
+ **/.void/
5
+ .eslintcache
6
+ .pnpm-debug.log
7
+ **/node_modules/
8
+ node_modules/
9
+ .env
10
+ coverage/
11
+ dist/
12
+ pnpm-lock.yaml
13
+ tsconfig.tsbuildinfo
14
+ vite.config.ts.timestamp-*
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "fate-app",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "description": "fate is a modern data client for React.",
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
+ "test:all": "vp check && vp test"
19
+ },
20
+ "dependencies": {
21
+ "@nkzw/fate": "latest",
22
+ "@void/react": "^0.7.13",
23
+ "graphql-sse": "^2.6.0",
24
+ "lucide-react": "^1.14.0",
25
+ "react": "^19.2.5",
26
+ "react-dom": "^19.2.5",
27
+ "react-error-boundary": "^6.1.1",
28
+ "react-fate": "latest",
29
+ "void": "^0.7.13"
30
+ },
31
+ "devDependencies": {
32
+ "@nkzw/eslint-plugin": "^2.0.0",
33
+ "@nkzw/oxlint-config": "^1.2.1",
34
+ "@rolldown/plugin-babel": "^0.2.3",
35
+ "@swc/core": "^1.15.33",
36
+ "@tailwindcss/vite": "^4.2.4",
37
+ "@types/node": "^25.6.0",
38
+ "@types/react": "^19.2.14",
39
+ "@types/react-dom": "^19.2.3",
40
+ "@typescript/native-preview": "7.0.0-dev.20260505.1",
41
+ "@vitejs/plugin-react": "^6.0.1",
42
+ "babel-plugin-react-compiler": "^1.0.0",
43
+ "eslint-plugin-no-only-tests": "^3.4.0",
44
+ "eslint-plugin-perfectionist": "^5.9.0",
45
+ "eslint-plugin-react-hooks": "^7.1.1",
46
+ "tailwindcss": "^4.2.4",
47
+ "ts-node": "^10.9.2",
48
+ "vite": "catalog:",
49
+ "vite-plus": "catalog:",
50
+ "vitest": "catalog:"
51
+ },
52
+ "browserslist": [
53
+ ">0.2%",
54
+ "not dead",
55
+ "not op_mini all"
56
+ ],
57
+ "engines": {
58
+ "node": ">=24.0.0",
59
+ "pnpm": ">=11.0.0"
60
+ },
61
+ "packageManager": "pnpm@11.0.6"
62
+ }
@@ -0,0 +1,69 @@
1
+ import { ConnectionRef, useListView, useRequest } from 'react-fate';
2
+ import { postDataView, userDataView } from '../src/fate/graphql.ts';
3
+
4
+ const PostConnectionView = {
5
+ args: { first: 10 },
6
+ items: {
7
+ node: postDataView,
8
+ },
9
+ pagination: {
10
+ hasNext: true,
11
+ },
12
+ };
13
+
14
+ const PostList = ({ posts: postsRef }: { posts: ConnectionRef<'Post'> }) => {
15
+ const [posts] = useListView(PostConnectionView, postsRef);
16
+
17
+ return (
18
+ <section className="space-y-4">
19
+ <h2 className="text-2xl font-semibold">Posts</h2>
20
+ {posts.map(({ node }) => (
21
+ <article
22
+ className="rounded-lg border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900"
23
+ key={node.id}
24
+ >
25
+ <h3 className="text-lg font-semibold">{node.title}</h3>
26
+ {node.author ? (
27
+ <p className="mt-2 text-sm text-slate-500 dark:text-slate-400">
28
+ {node.author.name ?? node.author.username ?? node.author.id}
29
+ </p>
30
+ ) : null}
31
+ </article>
32
+ ))}
33
+ </section>
34
+ );
35
+ };
36
+
37
+ export default function HomePage() {
38
+ const { posts, viewer } = useRequest({
39
+ posts: { list: PostConnectionView },
40
+ viewer: { view: userDataView },
41
+ });
42
+
43
+ return (
44
+ <main className="mx-auto min-h-screen max-w-4xl space-y-8 px-6 py-16">
45
+ <header className="space-y-3">
46
+ <p className="text-sm font-semibold tracking-wide text-blue-600 uppercase dark:text-blue-400">
47
+ fate + GraphQL
48
+ </p>
49
+ <h1 className="text-4xl font-semibold tracking-tight text-slate-950 dark:text-white">
50
+ Use fate with an existing GraphQL server
51
+ </h1>
52
+ <p className="max-w-2xl text-slate-600 dark:text-slate-300">
53
+ Replace the sample views in <code>src/fate/graphql.ts</code> with your schema types and
54
+ point <code>VITE_GRAPHQL_URL</code> at your API.
55
+ </p>
56
+ </header>
57
+
58
+ {viewer ? (
59
+ <section className="rounded-lg border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
60
+ <h2 className="text-sm font-medium text-slate-500 dark:text-slate-400">Viewer</h2>
61
+ <p className="mt-2 text-lg font-semibold">
62
+ {viewer.name ?? viewer.username ?? viewer.id}
63
+ </p>
64
+ </section>
65
+ ) : null}
66
+ <PostList posts={posts} />
67
+ </main>
68
+ );
69
+ }
@@ -0,0 +1,41 @@
1
+ import '../src/App.css';
2
+ import { ReactNode, Suspense, useMemo } from 'react';
3
+ import { ErrorBoundary } from 'react-error-boundary';
4
+ import { FateClient } from 'react-fate';
5
+ import { createFateClient } from 'react-fate/client';
6
+
7
+ const graphQLURL = import.meta.env.VITE_GRAPHQL_URL;
8
+ const graphQLLiveURL = import.meta.env.VITE_GRAPHQL_LIVE_URL;
9
+
10
+ const Loading = () => (
11
+ <main className="mx-auto flex min-h-screen max-w-4xl items-center px-6 py-16 text-slate-500">
12
+ Loading...
13
+ </main>
14
+ );
15
+
16
+ export default function Layout({ children }: { children: ReactNode }) {
17
+ const fate = useMemo(
18
+ () =>
19
+ createFateClient({
20
+ live: graphQLLiveURL ? { url: graphQLLiveURL } : false,
21
+ url: graphQLURL,
22
+ }),
23
+ [],
24
+ );
25
+
26
+ return (
27
+ <FateClient client={fate}>
28
+ <ErrorBoundary
29
+ fallbackRender={({ error }) => (
30
+ <main className="mx-auto min-h-screen max-w-4xl px-6 py-16">
31
+ <div className="rounded-lg border border-red-200 bg-red-50 p-5 text-red-950 dark:border-red-900 dark:bg-red-950 dark:text-red-50">
32
+ {error instanceof Error ? error.message : String(error)}
33
+ </div>
34
+ </main>
35
+ )}
36
+ >
37
+ <Suspense fallback={<Loading />}>{children}</Suspense>
38
+ </ErrorBoundary>
39
+ </FateClient>
40
+ );
41
+ }
@@ -0,0 +1,38 @@
1
+ packages:
2
+ - .
3
+
4
+ allowBuilds:
5
+ '@swc/core': true
6
+ esbuild: true
7
+ sharp: true
8
+ workerd: true
9
+
10
+ catalog:
11
+ vite: npm:@voidzero-dev/vite-plus-core@^0.1.23
12
+ vite-plus: ^0.1.23
13
+ vitest: npm:@voidzero-dev/vite-plus-test@^0.1.23
14
+
15
+ minimumReleaseAgeExclude:
16
+ - '@voidzero-dev/vite-plus-core'
17
+ - '@voidzero-dev/vite-plus-test'
18
+ - vite-plus
19
+
20
+ onlyBuiltDependencies:
21
+ - '@swc/core'
22
+ - '@tailwindcss/oxide'
23
+ - esbuild
24
+ - msgpackr-extract
25
+ - sharp
26
+ - workerd
27
+
28
+ overrides:
29
+ vite: 'catalog:'
30
+ vitest: 'catalog:'
31
+
32
+ peerDependencyRules:
33
+ allowAny:
34
+ - vite
35
+ - vitest
36
+ allowedVersions:
37
+ vite: '*'
38
+ vitest: '*'