create-fate 0.0.0 → 1.0.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 (266) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/bin/create-fate.mjs +412 -0
  4. package/package.json +26 -3
  5. package/templates/fate/README.md +8 -0
  6. package/templates/fate/drizzle/.github/workflows/push.yml +27 -0
  7. package/templates/fate/drizzle/.oxfmtrc.jsonc +23 -0
  8. package/templates/fate/drizzle/.vite-hooks/pre-commit +1 -0
  9. package/templates/fate/drizzle/.vscode/extensions.json +12 -0
  10. package/templates/fate/drizzle/.vscode/settings.json +25 -0
  11. package/templates/fate/drizzle/AGENTS.md +55 -0
  12. package/templates/fate/drizzle/README.md +112 -0
  13. package/templates/fate/drizzle/_gitignore +20 -0
  14. package/templates/fate/drizzle/client/package.json +58 -0
  15. package/templates/fate/drizzle/client/pages/index.tsx +96 -0
  16. package/templates/fate/drizzle/client/pages/layout.tsx +104 -0
  17. package/templates/fate/drizzle/client/pages/login.tsx +107 -0
  18. package/templates/fate/drizzle/client/pages/post/[id].tsx +22 -0
  19. package/templates/fate/drizzle/client/pages/search.tsx +95 -0
  20. package/templates/fate/drizzle/client/public/icon.svg +1 -0
  21. package/templates/fate/drizzle/client/src/App.css +153 -0
  22. package/templates/fate/drizzle/client/src/lib/AvailableLanguages.tsx +5 -0
  23. package/templates/fate/drizzle/client/src/lib/cx.tsx +6 -0
  24. package/templates/fate/drizzle/client/src/lib/env.tsx +5 -0
  25. package/templates/fate/drizzle/client/src/ui/Badge.tsx +31 -0
  26. package/templates/fate/drizzle/client/src/ui/Button.tsx +108 -0
  27. package/templates/fate/drizzle/client/src/ui/Card.tsx +32 -0
  28. package/templates/fate/drizzle/client/src/ui/CommentCard.tsx +75 -0
  29. package/templates/fate/drizzle/client/src/ui/Error.tsx +19 -0
  30. package/templates/fate/drizzle/client/src/ui/H2.tsx +15 -0
  31. package/templates/fate/drizzle/client/src/ui/H3.tsx +20 -0
  32. package/templates/fate/drizzle/client/src/ui/Header.tsx +96 -0
  33. package/templates/fate/drizzle/client/src/ui/Input.tsx +17 -0
  34. package/templates/fate/drizzle/client/src/ui/Link.tsx +11 -0
  35. package/templates/fate/drizzle/client/src/ui/LocaleSwitcher.tsx +52 -0
  36. package/templates/fate/drizzle/client/src/ui/PostCard.tsx +284 -0
  37. package/templates/fate/drizzle/client/src/ui/Section.tsx +23 -0
  38. package/templates/fate/drizzle/client/src/ui/UserCard.tsx +131 -0
  39. package/templates/fate/drizzle/client/src/user/AuthClient.tsx +6 -0
  40. package/templates/fate/drizzle/client/translations/de_DE.json +689 -0
  41. package/templates/fate/drizzle/client/translations/ja_JP.json +689 -0
  42. package/templates/fate/drizzle/client/vite.config.ts +39 -0
  43. package/templates/fate/drizzle/client/void.json +29 -0
  44. package/templates/fate/drizzle/docker-compose.yml +11 -0
  45. package/templates/fate/drizzle/package.json +46 -0
  46. package/templates/fate/drizzle/pnpm-workspace.yaml +36 -0
  47. package/templates/fate/drizzle/public/fate-logo-dark.svg +4 -0
  48. package/templates/fate/drizzle/public/fate-logo.svg +4 -0
  49. package/templates/fate/drizzle/server/.env.example +5 -0
  50. package/templates/fate/drizzle/server/drizzle.config.ts +11 -0
  51. package/templates/fate/drizzle/server/package.json +48 -0
  52. package/templates/fate/drizzle/server/src/__tests__/index.test.tsx +5 -0
  53. package/templates/fate/drizzle/server/src/app.tsx +44 -0
  54. package/templates/fate/drizzle/server/src/drizzle/db.ts +25 -0
  55. package/templates/fate/drizzle/server/src/drizzle/queries.ts +89 -0
  56. package/templates/fate/drizzle/server/src/drizzle/schema.ts +174 -0
  57. package/templates/fate/drizzle/server/src/drizzle/seed.tsx +77 -0
  58. package/templates/fate/drizzle/server/src/drizzle/seedData.ts +205 -0
  59. package/templates/fate/drizzle/server/src/index.tsx +24 -0
  60. package/templates/fate/drizzle/server/src/lib/auth.ts +33 -0
  61. package/templates/fate/drizzle/server/src/lib/env.ts +3 -0
  62. package/templates/fate/drizzle/server/src/lib/origins.ts +18 -0
  63. package/templates/fate/drizzle/server/src/router.ts +15 -0
  64. package/templates/fate/drizzle/server/src/trpc/connection.ts +4 -0
  65. package/templates/fate/drizzle/server/src/trpc/context.ts +22 -0
  66. package/templates/fate/drizzle/server/src/trpc/init.ts +51 -0
  67. package/templates/fate/drizzle/server/src/trpc/routers/comment.ts +146 -0
  68. package/templates/fate/drizzle/server/src/trpc/routers/post.ts +99 -0
  69. package/templates/fate/drizzle/server/src/trpc/routers/user.ts +68 -0
  70. package/templates/fate/drizzle/server/src/trpc/views.ts +75 -0
  71. package/templates/fate/drizzle/server/src/user/SessionUser.tsx +17 -0
  72. package/templates/fate/drizzle/server/vite.config.ts +8 -0
  73. package/templates/fate/drizzle/tsconfig.json +33 -0
  74. package/templates/fate/drizzle/vite.config.ts +71 -0
  75. package/templates/fate/http/.github/workflows/push.yml +27 -0
  76. package/templates/fate/http/.oxfmtrc.jsonc +23 -0
  77. package/templates/fate/http/.vite-hooks/pre-commit +1 -0
  78. package/templates/fate/http/.vscode/extensions.json +12 -0
  79. package/templates/fate/http/.vscode/settings.json +25 -0
  80. package/templates/fate/http/AGENTS.md +55 -0
  81. package/templates/fate/http/README.md +112 -0
  82. package/templates/fate/http/_gitignore +20 -0
  83. package/templates/fate/http/client/package.json +56 -0
  84. package/templates/fate/http/client/pages/index.tsx +96 -0
  85. package/templates/fate/http/client/pages/layout.tsx +89 -0
  86. package/templates/fate/http/client/pages/login.tsx +107 -0
  87. package/templates/fate/http/client/pages/post/[id].tsx +22 -0
  88. package/templates/fate/http/client/pages/search.tsx +95 -0
  89. package/templates/fate/http/client/public/icon.svg +1 -0
  90. package/templates/fate/http/client/src/App.css +153 -0
  91. package/templates/fate/http/client/src/lib/AvailableLanguages.tsx +5 -0
  92. package/templates/fate/http/client/src/lib/cx.tsx +6 -0
  93. package/templates/fate/http/client/src/lib/env.tsx +5 -0
  94. package/templates/fate/http/client/src/ui/Badge.tsx +31 -0
  95. package/templates/fate/http/client/src/ui/Button.tsx +108 -0
  96. package/templates/fate/http/client/src/ui/Card.tsx +32 -0
  97. package/templates/fate/http/client/src/ui/CommentCard.tsx +75 -0
  98. package/templates/fate/http/client/src/ui/Error.tsx +19 -0
  99. package/templates/fate/http/client/src/ui/H2.tsx +15 -0
  100. package/templates/fate/http/client/src/ui/H3.tsx +20 -0
  101. package/templates/fate/http/client/src/ui/Header.tsx +96 -0
  102. package/templates/fate/http/client/src/ui/Input.tsx +17 -0
  103. package/templates/fate/http/client/src/ui/Link.tsx +11 -0
  104. package/templates/fate/http/client/src/ui/LocaleSwitcher.tsx +52 -0
  105. package/templates/fate/http/client/src/ui/PostCard.tsx +281 -0
  106. package/templates/fate/http/client/src/ui/Section.tsx +23 -0
  107. package/templates/fate/http/client/src/ui/UserCard.tsx +131 -0
  108. package/templates/fate/http/client/src/user/AuthClient.tsx +6 -0
  109. package/templates/fate/http/client/translations/de_DE.json +689 -0
  110. package/templates/fate/http/client/translations/ja_JP.json +689 -0
  111. package/templates/fate/http/client/vite.config.ts +40 -0
  112. package/templates/fate/http/client/void.json +29 -0
  113. package/templates/fate/http/docker-compose.yml +11 -0
  114. package/templates/fate/http/package.json +46 -0
  115. package/templates/fate/http/pnpm-workspace.yaml +36 -0
  116. package/templates/fate/http/public/fate-logo-dark.svg +4 -0
  117. package/templates/fate/http/public/fate-logo.svg +4 -0
  118. package/templates/fate/http/server/.env.example +5 -0
  119. package/templates/fate/http/server/drizzle.config.ts +11 -0
  120. package/templates/fate/http/server/package.json +46 -0
  121. package/templates/fate/http/server/src/__tests__/index.test.tsx +5 -0
  122. package/templates/fate/http/server/src/app.tsx +36 -0
  123. package/templates/fate/http/server/src/drizzle/db.ts +25 -0
  124. package/templates/fate/http/server/src/drizzle/queries.ts +89 -0
  125. package/templates/fate/http/server/src/drizzle/schema.ts +174 -0
  126. package/templates/fate/http/server/src/drizzle/seed.tsx +77 -0
  127. package/templates/fate/http/server/src/drizzle/seedData.ts +205 -0
  128. package/templates/fate/http/server/src/fate/context.ts +20 -0
  129. package/templates/fate/http/server/src/fate/server.ts +285 -0
  130. package/templates/fate/http/server/src/fate/views.ts +75 -0
  131. package/templates/fate/http/server/src/index.tsx +24 -0
  132. package/templates/fate/http/server/src/lib/auth.ts +33 -0
  133. package/templates/fate/http/server/src/lib/env.ts +3 -0
  134. package/templates/fate/http/server/src/lib/origins.ts +18 -0
  135. package/templates/fate/http/server/src/router.ts +2 -0
  136. package/templates/fate/http/server/src/user/SessionUser.tsx +17 -0
  137. package/templates/fate/http/server/vite.config.ts +8 -0
  138. package/templates/fate/http/tsconfig.json +33 -0
  139. package/templates/fate/http/vite.config.ts +71 -0
  140. package/templates/fate/prisma/.github/workflows/push.yml +27 -0
  141. package/templates/fate/prisma/.oxfmtrc.jsonc +23 -0
  142. package/templates/fate/prisma/.vite-hooks/pre-commit +1 -0
  143. package/templates/fate/prisma/.vscode/extensions.json +12 -0
  144. package/templates/fate/prisma/.vscode/settings.json +25 -0
  145. package/templates/fate/prisma/AGENTS.md +55 -0
  146. package/templates/fate/prisma/README.md +126 -0
  147. package/templates/fate/prisma/_gitignore +20 -0
  148. package/templates/fate/prisma/client/package.json +58 -0
  149. package/templates/fate/prisma/client/pages/index.tsx +96 -0
  150. package/templates/fate/prisma/client/pages/layout.tsx +94 -0
  151. package/templates/fate/prisma/client/pages/login.tsx +107 -0
  152. package/templates/fate/prisma/client/pages/post/[id].tsx +22 -0
  153. package/templates/fate/prisma/client/pages/search.tsx +95 -0
  154. package/templates/fate/prisma/client/public/icon.svg +1 -0
  155. package/templates/fate/prisma/client/src/App.css +153 -0
  156. package/templates/fate/prisma/client/src/lib/AvailableLanguages.tsx +5 -0
  157. package/templates/fate/prisma/client/src/lib/cx.tsx +6 -0
  158. package/templates/fate/prisma/client/src/lib/env.tsx +5 -0
  159. package/templates/fate/prisma/client/src/ui/Badge.tsx +31 -0
  160. package/templates/fate/prisma/client/src/ui/Button.tsx +108 -0
  161. package/templates/fate/prisma/client/src/ui/Card.tsx +32 -0
  162. package/templates/fate/prisma/client/src/ui/CommentCard.tsx +75 -0
  163. package/templates/fate/prisma/client/src/ui/Error.tsx +19 -0
  164. package/templates/fate/prisma/client/src/ui/H2.tsx +15 -0
  165. package/templates/fate/prisma/client/src/ui/H3.tsx +20 -0
  166. package/templates/fate/prisma/client/src/ui/Header.tsx +96 -0
  167. package/templates/fate/prisma/client/src/ui/Input.tsx +17 -0
  168. package/templates/fate/prisma/client/src/ui/Link.tsx +11 -0
  169. package/templates/fate/prisma/client/src/ui/LocaleSwitcher.tsx +52 -0
  170. package/templates/fate/prisma/client/src/ui/PostCard.tsx +281 -0
  171. package/templates/fate/prisma/client/src/ui/Section.tsx +23 -0
  172. package/templates/fate/prisma/client/src/ui/UserCard.tsx +131 -0
  173. package/templates/fate/prisma/client/src/user/AuthClient.tsx +6 -0
  174. package/templates/fate/prisma/client/translations/de_DE.json +689 -0
  175. package/templates/fate/prisma/client/translations/ja_JP.json +689 -0
  176. package/templates/fate/prisma/client/vite.config.ts +39 -0
  177. package/templates/fate/prisma/client/void.json +29 -0
  178. package/templates/fate/prisma/docker-compose.yml +11 -0
  179. package/templates/fate/prisma/package.json +47 -0
  180. package/templates/fate/prisma/pnpm-workspace.yaml +39 -0
  181. package/templates/fate/prisma/public/fate-logo-dark.svg +4 -0
  182. package/templates/fate/prisma/public/fate-logo.svg +4 -0
  183. package/templates/fate/prisma/server/.env.example +5 -0
  184. package/templates/fate/prisma/server/package.json +42 -0
  185. package/templates/fate/prisma/server/prisma.config.ts +21 -0
  186. package/templates/fate/prisma/server/src/__tests__/index.test.tsx +5 -0
  187. package/templates/fate/prisma/server/src/app.tsx +40 -0
  188. package/templates/fate/prisma/server/src/index.tsx +24 -0
  189. package/templates/fate/prisma/server/src/lib/auth.ts +31 -0
  190. package/templates/fate/prisma/server/src/lib/env.ts +3 -0
  191. package/templates/fate/prisma/server/src/lib/origins.ts +18 -0
  192. package/templates/fate/prisma/server/src/prisma/prisma.ts +9 -0
  193. package/templates/fate/prisma/server/src/prisma/schema.prisma +98 -0
  194. package/templates/fate/prisma/server/src/prisma/seed.tsx +71 -0
  195. package/templates/fate/prisma/server/src/prisma/seedData.ts +205 -0
  196. package/templates/fate/prisma/server/src/router.ts +14 -0
  197. package/templates/fate/prisma/server/src/trpc/connection.ts +4 -0
  198. package/templates/fate/prisma/server/src/trpc/context.ts +22 -0
  199. package/templates/fate/prisma/server/src/trpc/init.ts +8 -0
  200. package/templates/fate/prisma/server/src/trpc/routers/comment.ts +176 -0
  201. package/templates/fate/prisma/server/src/trpc/routers/post.ts +154 -0
  202. package/templates/fate/prisma/server/src/trpc/routers/user.ts +71 -0
  203. package/templates/fate/prisma/server/src/trpc/views.ts +77 -0
  204. package/templates/fate/prisma/server/src/user/SessionUser.tsx +17 -0
  205. package/templates/fate/prisma/server/vite.config.ts +8 -0
  206. package/templates/fate/prisma/tsconfig.json +33 -0
  207. package/templates/fate/prisma/vite.config.ts +72 -0
  208. package/templates/fate/void/.oxfmtrc.jsonc +14 -0
  209. package/templates/fate/void/README.md +55 -0
  210. package/templates/fate/void/auth.ts +22 -0
  211. package/templates/fate/void/db/migrations/20260508073357_initial_schema.sql +133 -0
  212. package/templates/fate/void/db/migrations/20260508120500_seed_void_demo.sql +170 -0
  213. package/templates/fate/void/db/migrations/meta/20260508073357_snapshot.json +868 -0
  214. package/templates/fate/void/db/migrations/meta/_journal.json +20 -0
  215. package/templates/fate/void/db/queries.ts +211 -0
  216. package/templates/fate/void/db/schema.ts +325 -0
  217. package/templates/fate/void/db/seed-auth.ts +31 -0
  218. package/templates/fate/void/db/seed.ts +163 -0
  219. package/templates/fate/void/middleware/01.shared.ts +29 -0
  220. package/templates/fate/void/package.json +65 -0
  221. package/templates/fate/void/pages/category/[id].tsx +23 -0
  222. package/templates/fate/void/pages/index.tsx +153 -0
  223. package/templates/fate/void/pages/layout.tsx +46 -0
  224. package/templates/fate/void/pages/login.tsx +103 -0
  225. package/templates/fate/void/pages/post/[id].tsx +20 -0
  226. package/templates/fate/void/pages/search.tsx +81 -0
  227. package/templates/fate/void/pnpm-workspace.yaml +31 -0
  228. package/templates/fate/void/public/.assetsignore +1 -0
  229. package/templates/fate/void/public/icon.svg +1 -0
  230. package/templates/fate/void/routes/fate-live.ts +8 -0
  231. package/templates/fate/void/routes/fate.ts +8 -0
  232. package/templates/fate/void/seedData.ts +535 -0
  233. package/templates/fate/void/src/App.css +130 -0
  234. package/templates/fate/void/src/fate/__tests__/commentPermissions.test.ts +31 -0
  235. package/templates/fate/void/src/fate/commentPermissions.ts +17 -0
  236. package/templates/fate/void/src/fate/context.ts +15 -0
  237. package/templates/fate/void/src/fate/live.ts +6 -0
  238. package/templates/fate/void/src/fate/server.ts +401 -0
  239. package/templates/fate/void/src/fate/views.ts +164 -0
  240. package/templates/fate/void/src/lib/cx.tsx +6 -0
  241. package/templates/fate/void/src/lib/formatLabel.tsx +6 -0
  242. package/templates/fate/void/src/lib/shared.ts +10 -0
  243. package/templates/fate/void/src/ui/Badge.tsx +31 -0
  244. package/templates/fate/void/src/ui/Button.tsx +92 -0
  245. package/templates/fate/void/src/ui/Card.tsx +29 -0
  246. package/templates/fate/void/src/ui/CategoryCard.tsx +105 -0
  247. package/templates/fate/void/src/ui/CommentCard.tsx +80 -0
  248. package/templates/fate/void/src/ui/CreatePost.tsx +111 -0
  249. package/templates/fate/void/src/ui/Error.tsx +19 -0
  250. package/templates/fate/void/src/ui/EventCard.tsx +152 -0
  251. package/templates/fate/void/src/ui/H2.tsx +15 -0
  252. package/templates/fate/void/src/ui/H3.tsx +20 -0
  253. package/templates/fate/void/src/ui/Header.tsx +96 -0
  254. package/templates/fate/void/src/ui/Input.tsx +33 -0
  255. package/templates/fate/void/src/ui/Link.tsx +11 -0
  256. package/templates/fate/void/src/ui/Number.tsx +5 -0
  257. package/templates/fate/void/src/ui/PostCard.tsx +294 -0
  258. package/templates/fate/void/src/ui/Section.tsx +23 -0
  259. package/templates/fate/void/src/ui/TagBadge.tsx +23 -0
  260. package/templates/fate/void/src/ui/UserCard.tsx +129 -0
  261. package/templates/fate/void/src/user/AuthClient.tsx +1 -0
  262. package/templates/fate/void/src/user/SessionUser.tsx +17 -0
  263. package/templates/fate/void/tsconfig.json +34 -0
  264. package/templates/fate/void/vite.config.ts +83 -0
  265. package/templates/fate/void/void.json +32 -0
  266. package/templates/fate/void/wrangler.jsonc +16 -0
@@ -0,0 +1,163 @@
1
+ import { styleText } from 'node:util';
2
+ import randomEntry from '@nkzw/core/randomEntry.js';
3
+ import { defineSeed } from 'void/seed';
4
+ import { categories, comments, events, posts, tags, users } from '../seedData.ts';
5
+ import { createEventRecord, createPostRecord } from './queries.ts';
6
+ import { category, comment, post, tag } from './schema.ts';
7
+ import { createSeedAuth } from './seed-auth.ts';
8
+
9
+ export default defineSeed<typeof import('./schema.ts')>(async ({ db }) => {
10
+ const auth = createSeedAuth(db);
11
+ const queryDb = db as unknown as Parameters<typeof createPostRecord>[1];
12
+ const [existingPost] = await db.select({ id: post.id }).from(post).limit(1);
13
+
14
+ console.log(styleText('bold', '› Seeding database...'));
15
+
16
+ if (existingPost) {
17
+ console.log(styleText(['green', 'bold'], '✓ Database already contains seed data.'));
18
+ return;
19
+ }
20
+
21
+ console.log(styleText('bold', `Creating users`));
22
+
23
+ for (const data of users) {
24
+ const { user } = await auth.api.createUser({
25
+ body: data,
26
+ });
27
+
28
+ console.log(` Created user ${styleText('blue', user.name)}.`);
29
+ }
30
+
31
+ const seededUsers = await db.query.user.findMany();
32
+ const usersByEmail = new Map(seededUsers.map((user) => [user.email, user]));
33
+
34
+ console.log(styleText('bold', `Creating categories and tags`));
35
+
36
+ const createdCategories = await Promise.all(
37
+ categories.map(async (entry) => {
38
+ const [created] = await db.insert(category).values(entry).returning();
39
+ return created;
40
+ }),
41
+ );
42
+ const createdTags = await Promise.all(
43
+ tags.map(async (entry) => {
44
+ const [created] = await db.insert(tag).values(entry).returning();
45
+ return created;
46
+ }),
47
+ );
48
+
49
+ const categoriesByName = new Map(createdCategories.map((category) => [category.name, category]));
50
+ const tagsByName = new Map(createdTags.map((tag) => [tag.name, tag]));
51
+
52
+ console.log(styleText('bold', `Seeding posts and comments`));
53
+
54
+ const createdPosts = await Promise.all(
55
+ posts.map((post) => {
56
+ const author = usersByEmail.get(post.authorEmail);
57
+
58
+ if (!author) {
59
+ throw new Error(`Missing seeded user for ${post.authorEmail}.`);
60
+ }
61
+
62
+ const category = categoriesByName.get(post.category);
63
+ const tagIds = post.tags.flatMap((name) => {
64
+ const id = tagsByName.get(name)?.id;
65
+ return id ? [id] : [];
66
+ });
67
+
68
+ return createPostRecord(
69
+ {
70
+ authorId: author.id,
71
+ categoryId: category?.id,
72
+ content: post.content,
73
+ likes: post.likes,
74
+ tagIds,
75
+ title: post.title,
76
+ },
77
+ queryDb,
78
+ );
79
+ }),
80
+ );
81
+
82
+ let index = 0;
83
+ for (const content of comments) {
84
+ const postId = createdPosts[index % createdPosts.length];
85
+ const author = randomEntry(seededUsers);
86
+
87
+ if (!postId) {
88
+ throw new Error('Failed to create seeded post.');
89
+ }
90
+
91
+ await db.insert(comment).values({
92
+ authorId: author?.id,
93
+ content,
94
+ postId,
95
+ });
96
+
97
+ index++;
98
+ }
99
+
100
+ console.log(
101
+ styleText(
102
+ ['green', 'bold'],
103
+ `✓ Created ${createdPosts.length} posts and ${comments.length} comments.`,
104
+ ),
105
+ );
106
+
107
+ console.log(styleText('bold', `Creating community events`));
108
+
109
+ const createdEvents = await Promise.all(
110
+ events.map((event) => {
111
+ const host = usersByEmail.get(event.hostEmail);
112
+
113
+ if (!host) {
114
+ throw new Error(`Missing seeded user for ${event.hostEmail}.`);
115
+ }
116
+
117
+ const attendees = event.attendees
118
+ .map((attendee) => {
119
+ const attendeeUser = usersByEmail.get(attendee.userEmail);
120
+
121
+ if (!attendeeUser) {
122
+ throw new Error(`Missing seeded user for ${attendee.userEmail}.`);
123
+ }
124
+
125
+ return {
126
+ notes: attendee.notes,
127
+ status: attendee.status,
128
+ userId: attendeeUser.id,
129
+ };
130
+ })
131
+ .filter(Boolean);
132
+
133
+ return createEventRecord(
134
+ {
135
+ attendees,
136
+ capacity: event.capacity,
137
+ description: event.description,
138
+ endAt: event.endAt,
139
+ hostId: host.id,
140
+ livestreamUrl: event.livestreamUrl,
141
+ location: event.location,
142
+ name: event.name,
143
+ startAt: event.startAt,
144
+ topics: [...event.topics],
145
+ type: event.type,
146
+ },
147
+ queryDb,
148
+ );
149
+ }),
150
+ );
151
+
152
+ console.log(
153
+ styleText(
154
+ ['green', 'bold'],
155
+ `✓ Created ${createdEvents.length} events with ${events.reduce(
156
+ (total, event) => total + event.attendees.length,
157
+ 0,
158
+ )} attendee records.`,
159
+ ),
160
+ );
161
+
162
+ console.log(styleText(['green', 'bold'], '✓ Done.'));
163
+ });
@@ -0,0 +1,29 @@
1
+ import { defineMiddleware } from 'void';
2
+ import { getSession } from 'void/auth';
3
+ import type { SharedData } from '../src/lib/shared.ts';
4
+
5
+ declare module 'void' {
6
+ interface CloudContextVariables {
7
+ shared: SharedData;
8
+ }
9
+ }
10
+
11
+ export default defineMiddleware(async (context, next) => {
12
+ const session = getSession();
13
+ const url = new URL(context.req.raw.url);
14
+
15
+ context.set('shared', {
16
+ auth: {
17
+ user: session?.user
18
+ ? {
19
+ email: session.user.email,
20
+ id: session.user.id,
21
+ name: session.user.name,
22
+ }
23
+ : null,
24
+ },
25
+ origin: url.origin,
26
+ });
27
+
28
+ await next();
29
+ });
@@ -0,0 +1,65 @@
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
+ "db:generate": "void db generate",
15
+ "db:migrate": "void db migrate",
16
+ "db:seed": "void db seed --file db/seed.ts",
17
+ "dev": "vp dev",
18
+ "dev:setup": "vp run prepare:void && vp run db:seed && vp run fate:generate",
19
+ "fate:generate": "vp exec fate generate",
20
+ "prepare:void": "void prepare"
21
+ },
22
+ "dependencies": {
23
+ "@nkzw/core": "^1.3.1",
24
+ "@nkzw/fate": "latest",
25
+ "@nkzw/stack": "^2.3.2",
26
+ "@radix-ui/react-slot": "^1.2.4",
27
+ "@void/react": "^0.7.5",
28
+ "better-auth": "^1.6.9",
29
+ "class-variance-authority": "^0.7.1",
30
+ "clsx": "^2.1.1",
31
+ "drizzle-orm": "^0.45.2",
32
+ "lucide-react": "^1.14.0",
33
+ "react": "^19.2.5",
34
+ "react-dom": "^19.2.5",
35
+ "react-error-boundary": "^6.1.1",
36
+ "react-fate": "latest",
37
+ "tailwind-merge": "^3.5.0",
38
+ "void": "^0.7.5",
39
+ "void-fate": "latest",
40
+ "zod": "^4.4.3"
41
+ },
42
+ "devDependencies": {
43
+ "@rolldown/plugin-babel": "^0.2.3",
44
+ "@tailwindcss/vite": "^4.2.4",
45
+ "@types/node": "^25.6.0",
46
+ "@types/react": "^19.2.14",
47
+ "@types/react-dom": "^19.2.3",
48
+ "@vitejs/plugin-react": "^6.0.1",
49
+ "babel-plugin-react-compiler": "^1.0.0",
50
+ "tailwindcss": "^4.2.4",
51
+ "ts-node": "^10.9.2",
52
+ "vite": "catalog:",
53
+ "vite-plus": "catalog:"
54
+ },
55
+ "browserslist": [
56
+ ">0.2%",
57
+ "not dead",
58
+ "not op_mini all"
59
+ ],
60
+ "engines": {
61
+ "node": ">=24.0.0",
62
+ "pnpm": ">=11.0.0"
63
+ },
64
+ "packageManager": "pnpm@11.0.6"
65
+ }
@@ -0,0 +1,23 @@
1
+ import { useParams } from '@void/react';
2
+ import { useRequest } from 'react-fate';
3
+ import CategoryCard, { CategoryView } from '../../src/ui/CategoryCard.tsx';
4
+ import Section from '../../src/ui/Section.tsx';
5
+
6
+ export default function CategoryPage() {
7
+ const { id } = useParams<{ id: string }>();
8
+
9
+ if (!id) {
10
+ throw new Error('fate: Category ID is required.');
11
+ }
12
+
13
+ const { category } = useRequest(
14
+ { category: { id, view: CategoryView } },
15
+ { mode: 'stale-while-revalidate' },
16
+ );
17
+
18
+ return (
19
+ <Section>
20
+ <CategoryCard category={category} />
21
+ </Section>
22
+ );
23
+ }
@@ -0,0 +1,153 @@
1
+ import Stack, { VStack } from '@nkzw/stack';
2
+ import { Link, useShared } from '@void/react';
3
+ import { CircleMinus, CirclePlus } from 'lucide-react';
4
+ import { Activity, useState } from 'react';
5
+ import { ConnectionRef, useListView, useRequest, ViewRef } from 'react-fate';
6
+ import cx from '../src/lib/cx.tsx';
7
+ import type { SharedData } from '../src/lib/shared.ts';
8
+ import { Button } from '../src/ui/Button.tsx';
9
+ import Card from '../src/ui/Card.tsx';
10
+ import CategoryCard, { CategoryView } from '../src/ui/CategoryCard.tsx';
11
+ import CreatePost from '../src/ui/CreatePost.tsx';
12
+ import EventCard, { EventView } from '../src/ui/EventCard.tsx';
13
+ import H2 from '../src/ui/H2.tsx';
14
+ import { PostCard, PostView } from '../src/ui/PostCard.tsx';
15
+ import Section from '../src/ui/Section.tsx';
16
+ import UserCard, { UserCardView } from '../src/ui/UserCard.tsx';
17
+
18
+ const PostConnectionView = {
19
+ args: { first: 3 },
20
+ items: {
21
+ node: PostView,
22
+ },
23
+ pagination: {
24
+ hasNext: true,
25
+ },
26
+ };
27
+
28
+ const PostFeed = ({
29
+ posts: postsRef,
30
+ user,
31
+ }: {
32
+ posts: ConnectionRef<'Post'>;
33
+ user: ViewRef<'User'> | null;
34
+ }) => {
35
+ const [showPostEditor, setShowPostEditor] = useState(false);
36
+ const [posts, loadNext] = useListView(PostConnectionView, postsRef);
37
+
38
+ return posts.length ? (
39
+ <VStack gap={16}>
40
+ <Stack alignCenter between gap={16}>
41
+ <H2 className="pl-5">Latest posts</H2>
42
+ <div>
43
+ <button
44
+ className={cx(
45
+ 'pr-5',
46
+ user ? 'active:translate-y-0.5 active:opacity-50' : 'pointer-events-none opacity-0',
47
+ )}
48
+ onClick={() => setShowPostEditor((showPostEditor) => !showPostEditor)}
49
+ >
50
+ {showPostEditor ? (
51
+ <CircleMinus className="h-6 w-6" />
52
+ ) : (
53
+ <CirclePlus className="h-6 w-6" />
54
+ )}
55
+ </button>
56
+ </div>
57
+ </Stack>
58
+ <VStack gap={32}>
59
+ <Activity mode={showPostEditor ? 'visible' : 'hidden'}>
60
+ <CreatePost user={user} />
61
+ </Activity>
62
+ {posts.map(({ node }) => (
63
+ <PostCard key={node.id} post={node} />
64
+ ))}
65
+ {loadNext ? (
66
+ <Stack center>
67
+ <Button onClick={loadNext} variant="ghost">
68
+ Load more posts
69
+ </Button>
70
+ </Stack>
71
+ ) : null}
72
+ </VStack>
73
+ </VStack>
74
+ ) : null;
75
+ };
76
+
77
+ const CategoryFeed = ({ categories }: { categories: Array<ViewRef<'Category'>> }) =>
78
+ categories.length ? (
79
+ <VStack gap={16}>
80
+ <H2 className="pl-5">Explore by theme</H2>
81
+ <VStack gap={24}>
82
+ {categories.map((category) => (
83
+ <CategoryCard category={category} key={category.id} />
84
+ ))}
85
+ </VStack>
86
+ </VStack>
87
+ ) : null;
88
+
89
+ const EventFeed = ({ events }: { events: Array<ViewRef<'Event'>> }) =>
90
+ events.length ? (
91
+ <VStack gap={16}>
92
+ <H2 className="pl-5">Events</H2>
93
+ <VStack gap={24}>
94
+ {events.map((event) => (
95
+ <EventCard event={event} key={event.id} />
96
+ ))}
97
+ </VStack>
98
+ </VStack>
99
+ ) : null;
100
+
101
+ export default function HomePage() {
102
+ const { auth } = useShared<SharedData>();
103
+ const user = auth.user;
104
+ const { categories, events, posts, viewer } = useRequest({
105
+ categories: { list: CategoryView },
106
+ events: { list: EventView },
107
+ posts: { list: PostConnectionView },
108
+ viewer: { view: UserCardView },
109
+ });
110
+
111
+ return (
112
+ <Section gap={32}>
113
+ <div className={cx('grid gap-8 lg:items-stretch', user ? 'lg:grid-cols-[1.6fr_1fr]' : '')}>
114
+ <Card className="border border-white/20 bg-linear-to-r from-blue-500 to-sky-500 text-white dark:from-blue-600 dark:to-sky-600">
115
+ <Stack alignCenter gap={12} wrap>
116
+ <span className="squircle bg-white/20 px-2 py-1 text-xs font-semibold tracking-widest uppercase">
117
+ <span className="lowercase italic">fate</span> demo
118
+ </span>
119
+ </Stack>
120
+ <div className="space-y-3">
121
+ <h1 className="text-3xl leading-tight font-semibold text-balance lg:text-4xl">
122
+ fate is a modern data client for React inspired by Relay and GraphQL.
123
+ </h1>
124
+ <p className="text-sm text-white/80 lg:text-base">
125
+ fate combines view composition, normalized caching, data masking, Async React
126
+ features, and type-safe data fetching.
127
+ </p>
128
+ </div>
129
+ <Stack alignCenter gap={12} wrap>
130
+ {!user && (
131
+ <>
132
+ <Button asChild size="sm" variant="secondary">
133
+ <Link className="squircle px-4 py-2 text-sm font-semibold" href="/login">
134
+ Login
135
+ </Link>
136
+ </Button>
137
+ <span className="text-sm text-white/80">Sign in to post comments.</span>
138
+ </>
139
+ )}
140
+ </Stack>
141
+ </Card>
142
+ {viewer && <UserCard viewer={viewer} />}
143
+ </div>
144
+ <div className="grid gap-8 lg:grid-cols-[1.6fr_1fr] lg:items-start">
145
+ <PostFeed posts={posts} user={viewer} />
146
+ <VStack gap={24}>
147
+ <CategoryFeed categories={categories} />
148
+ <EventFeed events={events} />
149
+ </VStack>
150
+ </div>
151
+ </Section>
152
+ );
153
+ }
@@ -0,0 +1,46 @@
1
+ import '../src/App.css';
2
+ import Stack from '@nkzw/stack';
3
+ import { useShared } from '@void/react';
4
+ import { ReactNode, Suspense } from 'react';
5
+ import { ErrorBoundary } from 'react-error-boundary';
6
+ import { VoidFateClient } from 'void-fate/react';
7
+ import type { SharedData } from '../src/lib/shared.ts';
8
+ import Card from '../src/ui/Card.tsx';
9
+ import Error from '../src/ui/Error.tsx';
10
+ import Header from '../src/ui/Header.tsx';
11
+ import Section from '../src/ui/Section.tsx';
12
+
13
+ const Thinking = () => (
14
+ <Section>
15
+ <Stack center className="animate-pulse text-gray-500 italic" verticalPadding={48}>
16
+ Thinking...
17
+ </Stack>
18
+ </Section>
19
+ );
20
+
21
+ export default function Layout({ children }: { children: ReactNode }) {
22
+ const shared = useShared<SharedData>();
23
+ const userId = shared.auth.user?.id;
24
+ const origin = typeof window === 'undefined' ? shared.origin : window.location.origin;
25
+
26
+ return (
27
+ <VoidFateClient origin={origin} userId={userId}>
28
+ <div className="min-h-screen bg-background text-foreground">
29
+ <div className="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%)]">
30
+ <Header />
31
+ <ErrorBoundary
32
+ fallbackRender={({ error }) => (
33
+ <Section>
34
+ <Card>
35
+ <Error error={error} />
36
+ </Card>
37
+ </Section>
38
+ )}
39
+ >
40
+ <Suspense fallback={<Thinking />}>{children}</Suspense>
41
+ </ErrorBoundary>
42
+ </div>
43
+ </div>
44
+ </VoidFateClient>
45
+ );
46
+ }
@@ -0,0 +1,103 @@
1
+ import Stack, { VStack } from '@nkzw/stack';
2
+ import { useRouter, useShared } from '@void/react';
3
+ import { ExternalLinkIcon } from 'lucide-react';
4
+ import { useActionState, useEffect, useState } from 'react';
5
+ import type { SharedData } from '../src/lib/shared.ts';
6
+ import { Button } from '../src/ui/Button.tsx';
7
+ import Card from '../src/ui/Card.tsx';
8
+ import H2 from '../src/ui/H2.tsx';
9
+ import Input from '../src/ui/Input.tsx';
10
+ import Section from '../src/ui/Section.tsx';
11
+ import AuthClient from '../src/user/AuthClient.tsx';
12
+
13
+ export default function LoginPage() {
14
+ const [email, setEmail] = useState('');
15
+ const [password, setPassword] = useState('');
16
+ const router = useRouter();
17
+ const { auth } = useShared<SharedData>();
18
+
19
+ const [, signInAction] = useActionState(async () => {
20
+ await AuthClient.signIn.email(
21
+ {
22
+ email,
23
+ password,
24
+ },
25
+ {
26
+ onError: () => {},
27
+ onRequest: () => {},
28
+ onSuccess: () => {
29
+ router.flushAll();
30
+ void router.visit('/', { replace: true });
31
+ },
32
+ },
33
+ );
34
+
35
+ return null;
36
+ }, null);
37
+
38
+ useEffect(() => {
39
+ if (auth.user) {
40
+ router.flushAll();
41
+ void router.visit('/', { replace: true });
42
+ }
43
+ }, [auth.user, router]);
44
+
45
+ if (auth.user) {
46
+ return null;
47
+ }
48
+
49
+ return (
50
+ <Section>
51
+ <VStack center gap={16}>
52
+ <H2 className="pl-5">Sign In</H2>
53
+ <Stack gap={32} wrap>
54
+ <Card className="w-84">
55
+ <Stack gap vertical>
56
+ <VStack action={signInAction} as="form" gap={12}>
57
+ <Input
58
+ className="w-48"
59
+ name="email"
60
+ onChange={(e) => setEmail(e.target.value)}
61
+ placeholder="email"
62
+ type="email"
63
+ value={email}
64
+ />
65
+ <Input
66
+ className="w-48"
67
+ name="password"
68
+ onChange={(e) => setPassword(e.target.value)}
69
+ placeholder="password"
70
+ type="password"
71
+ value={password}
72
+ />
73
+ <div>
74
+ <Button type="submit" variant="outline">
75
+ Sign In
76
+ </Button>
77
+ </div>
78
+ </VStack>
79
+ </Stack>
80
+ </Card>
81
+ <Card className="w-84">
82
+ <p>
83
+ Try one of the
84
+ <Stack
85
+ alignCenter
86
+ as="a"
87
+ className="inline-flex! px-1 underline hover:no-underline"
88
+ gap={4}
89
+ href="https://github.com/nkzw-tech/fate/blob/main/packages/create-fate/templates/fate/void/seedData.ts#L1"
90
+ rel="noreferrer"
91
+ target="_blank"
92
+ >
93
+ Example Accounts
94
+ <ExternalLinkIcon className="h-4 w-4" />
95
+ </Stack>{' '}
96
+ in the seed data.
97
+ </p>
98
+ </Card>
99
+ </Stack>
100
+ </VStack>
101
+ </Section>
102
+ );
103
+ }
@@ -0,0 +1,20 @@
1
+ import { useParams } from '@void/react';
2
+ import { useRequest } from 'react-fate';
3
+ import { PostCard, PostView } from '../../src/ui/PostCard.tsx';
4
+ import Section from '../../src/ui/Section.tsx';
5
+
6
+ export default function PostPage() {
7
+ const { id } = useParams<{ id: string }>();
8
+
9
+ if (!id) {
10
+ throw new Error('fate: Post ID is required.');
11
+ }
12
+
13
+ const { post } = useRequest({ post: { id, view: PostView } });
14
+
15
+ return (
16
+ <Section>
17
+ <PostCard detail post={post} />
18
+ </Section>
19
+ );
20
+ }
@@ -0,0 +1,81 @@
1
+ import Stack, { VStack } from '@nkzw/stack';
2
+ import { Suspense, useDeferredValue, useState } from 'react';
3
+ import { ErrorBoundary } from 'react-error-boundary';
4
+ import { useRequest, useView, view, ViewRef } from 'react-fate';
5
+ import type { Comment, Post } from '../src/fate/views.ts';
6
+ import cx from '../src/lib/cx.tsx';
7
+ import Card from '../src/ui/Card.tsx';
8
+ import CommentCard, { CommentView } from '../src/ui/CommentCard.tsx';
9
+ import Error from '../src/ui/Error.tsx';
10
+ import Input from '../src/ui/Input.tsx';
11
+ import Section from '../src/ui/Section.tsx';
12
+
13
+ const CommentPostView = view<Post>()({
14
+ commentCount: true,
15
+ id: true,
16
+ title: true,
17
+ });
18
+
19
+ const CommentSearchView = view<Comment>()({
20
+ ...CommentView,
21
+ id: true,
22
+ post: CommentPostView,
23
+ });
24
+
25
+ const CommentResult = ({ comment: commentRef }: { comment: ViewRef<'Comment'> }) => {
26
+ const comment = useView(CommentSearchView, commentRef);
27
+ const post = useView(CommentPostView, comment.post);
28
+
29
+ return <CommentCard comment={comment} link post={post} />;
30
+ };
31
+
32
+ const SearchResults = ({ isStale, query }: { isStale: boolean; query: string }) => {
33
+ const { commentSearch } = useRequest({
34
+ commentSearch: { args: { query }, list: CommentSearchView },
35
+ });
36
+
37
+ if (commentSearch.length === 0) {
38
+ return (
39
+ <p>
40
+ No matches for <i>&quot;{query}&quot;</i>
41
+ </p>
42
+ );
43
+ }
44
+
45
+ return (
46
+ <VStack className={cx(isStale && 'opacity-50')} gap={12}>
47
+ {commentSearch.map((comment) => (
48
+ <CommentResult comment={comment} key={comment.id} />
49
+ ))}
50
+ </VStack>
51
+ );
52
+ };
53
+
54
+ export default function SearchPage() {
55
+ const [query, setQuery] = useState('');
56
+ const deferredQuery = useDeferredValue(query);
57
+ const isStale = query !== deferredQuery;
58
+
59
+ return (
60
+ <Section>
61
+ <Card>
62
+ <Stack alignCenter between gap={16}>
63
+ <Input
64
+ className="w-64"
65
+ onChange={(e) => setQuery(e.target.value)}
66
+ placeholder="Search comments..."
67
+ ref={(ref) => ref?.focus()}
68
+ value={query}
69
+ />
70
+ <div className="text-xs text-muted-foreground">500ms artificial slowdown</div>
71
+ </Stack>
72
+
73
+ <ErrorBoundary FallbackComponent={Error}>
74
+ <Suspense fallback={<h2>Thinking…</h2>}>
75
+ {query.trim().length > 0 ? <SearchResults isStale={isStale} query={query} /> : null}
76
+ </Suspense>
77
+ </ErrorBoundary>
78
+ </Card>
79
+ </Section>
80
+ );
81
+ }