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,66 @@
1
+ import { betterAuth } from 'better-auth';
2
+ import { drizzleAdapter } from 'better-auth/adapters/drizzle';
3
+ import { admin, username } from 'better-auth/plugins';
4
+ import { db } from '../../db/db.ts';
5
+ import schema from '../../db/schema.ts';
6
+ import { trustedOrigins } from './origins.ts';
7
+
8
+ const defaultSecret = 'fate-cloudflare-development-secret';
9
+ const betterAuthDefaultSecret = 'better-auth-secret-12345678901234567890';
10
+
11
+ const resolveSecret = (secret?: string) => {
12
+ const value =
13
+ secret ??
14
+ (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env
15
+ ?.BETTER_AUTH_SECRET;
16
+
17
+ return value && value !== betterAuthDefaultSecret ? value : defaultSecret;
18
+ };
19
+
20
+ const createAuthInstance = (baseURL: string, secret: string) =>
21
+ betterAuth({
22
+ advanced: {
23
+ database: {
24
+ generateId: false,
25
+ },
26
+ },
27
+ basePath: '/api/auth',
28
+ baseURL,
29
+ database: drizzleAdapter(db, {
30
+ provider: 'sqlite',
31
+ schema,
32
+ }),
33
+ emailAndPassword: {
34
+ autoSignIn: true,
35
+ enabled: true,
36
+ maxPasswordLength: 128,
37
+ minPasswordLength: 8,
38
+ },
39
+ plugins: [admin(), username()],
40
+ secret,
41
+ session: {
42
+ cookieCache: {
43
+ enabled: true,
44
+ maxAge: 15 * 24 * 60 * 60,
45
+ },
46
+ },
47
+ telemetry: { enabled: false },
48
+ trustedOrigins,
49
+ });
50
+
51
+ const authByBaseURL = new Map<string, ReturnType<typeof createAuthInstance>>();
52
+
53
+ export const createAuth = (baseURL = 'http://localhost:8787', secret?: string) => {
54
+ const resolvedSecret = resolveSecret(secret);
55
+ const cacheKey = `${baseURL}\0${resolvedSecret}`;
56
+ const existing = authByBaseURL.get(cacheKey);
57
+ if (existing) {
58
+ return existing;
59
+ }
60
+
61
+ const auth = createAuthInstance(baseURL, resolvedSecret);
62
+ authByBaseURL.set(cacheKey, auth);
63
+ return auth;
64
+ };
65
+
66
+ export const auth = createAuth();
@@ -0,0 +1,17 @@
1
+ const localDevOriginPattern = /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?$/;
2
+
3
+ export const clientOrigin = 'http://localhost:6001';
4
+
5
+ export const resolveCorsOrigin = (origin: string | null) => {
6
+ if (!origin) {
7
+ return null;
8
+ }
9
+
10
+ if (origin === clientOrigin || localDevOriginPattern.test(origin)) {
11
+ return origin;
12
+ }
13
+
14
+ return null;
15
+ };
16
+
17
+ export const trustedOrigins = [clientOrigin, 'http://localhost:*', 'http://127.0.0.1:*'];
@@ -0,0 +1,2 @@
1
+ export { fateServer } from './fate/server.ts';
2
+ export * from './fate/views.ts';
@@ -0,0 +1,17 @@
1
+ import type { UserRow } from '../../db/schema.ts';
2
+
3
+ export type SessionUser = Pick<UserRow, '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,10 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "target": "ES2022",
7
+ "types": ["vite/client"]
8
+ },
9
+ "include": ["db/**/*.ts", "src/**/*.ts", "src/**/*.tsx"]
10
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ "name": "fate-cloudflare",
4
+ "main": "src/index.ts",
5
+ "compatibility_date": "2026-06-05",
6
+ "compatibility_flags": ["nodejs_compat"],
7
+ "d1_databases": [
8
+ {
9
+ "binding": "DB",
10
+ "database_name": "fate-cloudflare",
11
+ "database_id": "00000000-0000-0000-0000-000000000000",
12
+ "migrations_dir": "db/migrations",
13
+ },
14
+ ],
15
+ "durable_objects": {
16
+ "bindings": [
17
+ {
18
+ "name": "FATE_LIVE",
19
+ "class_name": "FateLiveDurableObject",
20
+ },
21
+ ],
22
+ },
23
+ "migrations": [
24
+ {
25
+ "tag": "fate-live-v1",
26
+ "new_sqlite_classes": ["FateLiveDurableObject"],
27
+ },
28
+ ],
29
+ }
@@ -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,72 @@
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/.wrangler/',
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/.wrangler',
45
+ 'server/db/migrations/meta/**',
46
+ 'vite.config.ts.timestamp-*',
47
+ 'client/vite.config.ts.timestamp-*',
48
+ ],
49
+ options: { typeAware: true, typeCheck: true },
50
+ overrides: [
51
+ {
52
+ files: ['server/src/index.ts', '**/__tests__/**'],
53
+ rules: {
54
+ 'no-console': 'off',
55
+ },
56
+ },
57
+ ],
58
+ rules: {
59
+ '@typescript-eslint/no-explicit-any': 'off',
60
+ },
61
+ },
62
+ run: {
63
+ tasks: {
64
+ 'test:all': {
65
+ command: 'vp check && vp test',
66
+ },
67
+ },
68
+ },
69
+ staged: {
70
+ '*': 'vp check --fix',
71
+ },
72
+ });
@@ -1,16 +1,10 @@
1
- <p align="center">
2
- <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="/public/fate-logo-dark.svg">
4
- <source media="(prefers-color-scheme: light)" srcset="/public/fate-logo.svg">
5
- <img alt="Logo" src="/public/fate-logo.svg" width="50%">
6
- </picture>
7
- </p>
1
+ # _fate_
8
2
 
9
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.
10
4
 
11
5
  Check out [fate.technology](https://fate.technology) for documentation, examples, and guides.
12
6
 
13
- # _fate_ Quick Start Template
7
+ ## Quick Start Template
14
8
 
15
9
  Create a new fate app with Vite+:
16
10
 
@@ -37,7 +37,7 @@ const LocaleContext = createLocaleContext({
37
37
  });
38
38
 
39
39
  export default function Layout({ children }: { children: ReactNode }) {
40
- const { data: session, isPending } = AuthClient.useSession();
40
+ const { data: session } = AuthClient.useSession();
41
41
  const userId = session?.user.id;
42
42
 
43
43
  const fate = useMemo(
@@ -72,7 +72,7 @@ export default function Layout({ children }: { children: ReactNode }) {
72
72
  <div className="min-h-screen bg-background text-foreground">
73
73
  <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%)]">
74
74
  <Header />
75
- {isPending ? (
75
+ {session ? (
76
76
  <Thinking />
77
77
  ) : (
78
78
  <FateClient client={fate} key={userId}>
@@ -4,6 +4,7 @@ import { cx } from 'class-variance-authority';
4
4
  import { fbs } from 'fbtee';
5
5
  import {
6
6
  KeyboardEvent,
7
+ Suspense,
7
8
  startTransition,
8
9
  useActionState,
9
10
  useCallback,
@@ -11,7 +12,16 @@ import {
11
12
  useState,
12
13
  } from 'react';
13
14
  import { ErrorBoundary } from 'react-error-boundary';
14
- import { useFateClient, useLiveListView, useLiveView, useView, view, ViewRef } from 'react-fate';
15
+ import {
16
+ defer,
17
+ type Deferred,
18
+ useLiveListView,
19
+ useFateClient,
20
+ useLiveView,
21
+ useView,
22
+ view,
23
+ ViewRef,
24
+ } from 'react-fate';
15
25
  import { Button } from '../ui/Button.tsx';
16
26
  import Card from '../ui/Card.tsx';
17
27
  import AuthClient from '../user/AuthClient.tsx';
@@ -33,13 +43,36 @@ const CommentConnectionView = {
33
43
  export const PostView = view<Post>()({
34
44
  author: UserView,
35
45
  commentCount: true,
36
- comments: CommentConnectionView,
46
+ comments: defer(CommentConnectionView),
37
47
  content: true,
38
48
  id: true,
39
49
  likes: true,
40
50
  title: true,
41
51
  });
42
52
 
53
+ const PostComments = ({
54
+ comments,
55
+ post,
56
+ }: {
57
+ comments: Deferred<{ items: ReadonlyArray<{ node: ViewRef<'Comment'> }> }>;
58
+ post: { commentCount: number; id: string; title: string };
59
+ }) => {
60
+ const [items, loadNext] = useLiveListView(CommentConnectionView, comments);
61
+
62
+ return items.length > 0 ? (
63
+ <VStack gap={12}>
64
+ {items.map(({ node }) => (
65
+ <CommentCard comment={node} key={node.id} post={post} />
66
+ ))}
67
+ {loadNext ? (
68
+ <Button onClick={loadNext} variant="ghost">
69
+ <fbt desc="Load more comments button">Load more comments</fbt>
70
+ </Button>
71
+ ) : null}
72
+ </VStack>
73
+ ) : null;
74
+ };
75
+
43
76
  const CommentInput = ({
44
77
  error,
45
78
  post,
@@ -128,7 +161,6 @@ export function PostCard({ detail, post: postRef }: { detail?: boolean; post: Vi
128
161
  const fate = useFateClient();
129
162
  const post = useLiveView(PostView, postRef);
130
163
  const author = useView(UserView, post.author);
131
- const [comments, loadNext] = useLiveListView(CommentConnectionView, post.comments);
132
164
 
133
165
  const [likeResult, likeAction, likeIsPending] = useActionState(fate.actions.post.like, null);
134
166
 
@@ -262,18 +294,9 @@ export function PostCard({ detail, post: postRef }: { detail?: boolean; post: Vi
262
294
  </fbt:plural>
263
295
  </fbt>
264
296
  </h4>
265
- {comments.length > 0 ? (
266
- <VStack gap={12}>
267
- {comments.map(({ node }) => (
268
- <CommentCard comment={node} key={node.id} post={post} />
269
- ))}
270
- {loadNext ? (
271
- <Button onClick={loadNext} variant="ghost">
272
- <fbt desc="Load more comments button">Load more comments</fbt>
273
- </Button>
274
- ) : null}
275
- </VStack>
276
- ) : null}
297
+ <Suspense fallback={null}>
298
+ <PostComments comments={post.comments} post={post} />
299
+ </Suspense>
277
300
  <ErrorBoundary fallbackRender={({ error }) => <CommentInput error={error} post={post} />}>
278
301
  <CommentInput post={post} />
279
302
  </ErrorBoundary>
@@ -11,9 +11,14 @@ allowBuilds:
11
11
  workerd: true
12
12
 
13
13
  catalog:
14
- vite: npm:@voidzero-dev/vite-plus-core@^0.1.20
15
- vite-plus: ^0.1.20
16
- vitest: npm:@voidzero-dev/vite-plus-test@^0.1.20
14
+ vite: npm:@voidzero-dev/vite-plus-core@^0.1.23
15
+ vite-plus: ^0.1.23
16
+ vitest: npm:@voidzero-dev/vite-plus-test@^0.1.23
17
+
18
+ minimumReleaseAgeExclude:
19
+ - '@voidzero-dev/vite-plus-core'
20
+ - '@voidzero-dev/vite-plus-test'
21
+ - vite-plus
17
22
 
18
23
  onlyBuiltDependencies:
19
24
  - '@swc/core'
@@ -24,6 +29,7 @@ onlyBuiltDependencies:
24
29
  - unrs-resolver
25
30
 
26
31
  overrides:
32
+ kysely: 0.28.17
27
33
  vite: 'catalog:'
28
34
  vitest: 'catalog:'
29
35
 
@@ -3,6 +3,6 @@ import { defineConfig } from 'vite-plus';
3
3
  export default defineConfig({
4
4
  pack: {
5
5
  entry: ['./src/app.tsx'],
6
- outputOptions: { codeSplitting: false, file: 'dist/index.js' },
6
+ outputOptions: { codeSplitting: false, entryFileNames: 'index.js' },
7
7
  },
8
8
  });
@@ -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
@@ -0,0 +1 @@
1
+ vp staged
@@ -0,0 +1,12 @@
1
+ {
2
+ "recommendations": [
3
+ "dbaeumer.vscode-eslint",
4
+ "oxc.oxc-vscode",
5
+ "styled-components.vscode-styled-components",
6
+ "sysoev.vscode-open-in-github",
7
+ "tailwindcss.intellisense",
8
+ "TypeScriptTeam.native-preview",
9
+ "usernamehw.errorlens",
10
+ "wix.vscode-import-cost"
11
+ ]
12
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "editor.formatOnSave": true,
3
+ "files.insertFinalNewline": true,
4
+ "files.trimFinalNewlines": true,
5
+ "editor.codeActionsOnSave": {
6
+ "source.fixAll.eslint": "explicit"
7
+ },
8
+ "[javascript]": {
9
+ "editor.defaultFormatter": "oxc.oxc-vscode"
10
+ },
11
+ "[typescriptreact]": {
12
+ "editor.defaultFormatter": "oxc.oxc-vscode"
13
+ },
14
+ "[typescript]": {
15
+ "editor.defaultFormatter": "oxc.oxc-vscode"
16
+ },
17
+ "[json]": {
18
+ "editor.defaultFormatter": "oxc.oxc-vscode"
19
+ },
20
+ "typescript.preferences.importModuleSpecifierEnding": "js",
21
+ "typescript.reportStyleChecksAsWarnings": false,
22
+ "typescript.updateImportsOnFileMove.enabled": "always",
23
+ "typescript.preferences.autoImportFileExcludePatterns": ["**/node_modules/vitest"],
24
+ "typescript.experimental.useTsgo": true
25
+ }
@@ -0,0 +1,55 @@
1
+ # Using _fate_ for React applications
2
+
3
+ This guidance is for agents working in projects bootstrapped from the fate template. It focuses on the client-side patterns for fate's view composition and action execution.
4
+
5
+ ## Core Workflow
6
+
7
+ - **Define views with explicit selections:** Define reusable view objects with `view<T>()({...})`, compose them via spreads, and resolve them with `useView`.
8
+ - **Resolve data via references:** Avoid passing raw fetched objects; instead pass `ViewRef`s to downstream components to keep selections scoped and cache-aware. Then call `const result = useView(viewDefinition, ref)` to read only the selected fields while subscribing to cache updates. Name the prop after the result data, and rename it using destructuring in the function arguments: `const PostComponent = ({ post: postRef }: { post: ViewRef<'Post'>}) => { const post = useView(PostView, postRef); ... }`.
9
+ - **Request data at screen roots:** Use `useRequest` to compose all views required for a route or screen into a single typed request; wrap roots in `Suspense` and an error boundary since requests may suspend or throw.
10
+ - **Compose reusable views:** Extract nested selections into their own views (e.g., `UserView`) and compose them inside parent views (`author: UserView`) to avoid duplication and overfetching. This keeps selections DRY and ensures view updates propagate consistently when the cache changes.
11
+
12
+ ## Actions & Mutations
13
+
14
+ - **Prefer `fate.actions` with `useActionState`:** Trigger mutations through React Actions to integrate with Suspense/`useTransition`, receiving `[result, action, isPending]`. Call actions via the relevant data inputs, for example `action({input: { id: post.id }})`.
15
+ - **Optimistic updates by default:** Pass `optimistic` payloads to immediately update the normalized cache; only views selecting the changed fields re-render and failures roll back automatically. Optimistic payloads should match the expected server data changes of the associated object.
16
+ - **Select extra fields when side effects matter:** When a mutation affects related entities (e.g., comment count on a post), pass a `view` to the action to fetch the needed fields in the same round-trip and keep the cache coherent for all dependent views.
17
+ - **Imperative escape hatch:** Use `fate.mutations` when you must fire mutations outside React components or without waiting on prior actions; handle loading/error states yourself.
18
+ - **Handle deletions and resets explicitly:** Use the `delete: true` flag to evict records from the cache, and send the `'reset'` token to clear stale action errors without re-running the mutation.
19
+ - **Error handling model:** Treat expected errors (e.g., 404) at the call site via the action result; unexpected errors bubble to the nearest React error boundary.
20
+
21
+ ## Common Pitfalls
22
+
23
+ - **Use ViewRefs:** Pass references instead of raw data. Keep components focused on views and `ViewRef`s to let fate manage data masking and updates.
24
+ - **Co-locate data needs:** Keep view definitions near their components and compose them upward to the request root for predictable fetching.
25
+ - **Use Suspense:** Wrap pages in `Suspense`/`ErrorBoundary` instead of ad-hoc loading/error flags; fate aligns with Async React by design.
26
+ - **Do not duplicate types:** Keep generated GraphQL/Fate types and views imported from the server package. Avoid redefining entity shapes on the client. This ensures view selections stay type-safe end-to-end.
27
+ - **`useRequest` only at the root:** When adding routes or layouts, create a dedicated `useRequest` call per screen root that pulls together all child views; do not scatter `useRequest` across leaf components unless it's necessary to issue requests due to user input.
28
+ - **React Actions:** Prefer React Action-friendly UI primitives (buttons/forms that accept an `action` prop). If unavailable, wrap action calls with `startTransition` in `onClick` handlers.
29
+ - **fate client support** fate's local client support files are maintained by the Vite/fate tooling. Do not manually edit `.fate` files; make the proper schema changes on the server and run `pnpm fate:generate` when working outside Vite dev.
30
+ - **Library Versions:** This repository uses the most recent releases of React, fate, Vite, GraphQL, and more. You might not know about the new releases yet, please don't get confused. The versions you see are real, and there are no feature or version mismatches. You can search the internet for more information about them.
31
+
32
+ Full documentation can be found on the filesystem at `./client/node_modules/react-fate/README.md` or online at [fate.technology](https://fate.technology/).
33
+
34
+ ## Review Checklist for Agents
35
+
36
+ - [ ] Every component that reads server data does so through `useView` and receives a `ViewRef` prop.
37
+ - [ ] Root components gather data with a single `useRequest` call.
38
+ - [ ] Mutations are wired through `useActionState` with optimistic updates and necessary `view` selections; imperative `fate.mutations` are justified when used.
39
+ - [ ] Shared selections live in reusable views using regular JavaScript object spreads rather than duplicated field lists.
40
+
41
+ <!--VITE PLUS START-->
42
+
43
+ # Using Vite+, the Unified Toolchain for the Web
44
+
45
+ This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, and it invokes Vite through `vp dev` and `vp build`. Run `vp help` to print a list of commands and `vp <command> --help` for information about a specific command.
46
+
47
+ Docs are local at `node_modules/vite-plus/docs` or online at https://viteplus.dev/guide/.
48
+
49
+ ## Review Checklist
50
+
51
+ - [ ] Run `vp install` after pulling remote changes and before getting started.
52
+ - [ ] Run `vp check` and `vp test` to format, lint, type check and test changes.
53
+ - [ ] Check if there are `vite.config.ts` tasks or `package.json` scripts necessary for validation, run via `vp run <script>`.
54
+
55
+ <!--VITE PLUS END-->
@@ -0,0 +1,121 @@
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
+ Check out [fate.technology](https://fate.technology) for documentation, examples, and guides.
6
+
7
+ ## Quick Start Template
8
+
9
+ Create a new fate app with Vite+:
10
+
11
+ ```bash
12
+ vp create fate my-app --template graphql
13
+ ```
14
+
15
+ ## Getting Started with _fate_
16
+
17
+ Read the [Getting Started Guide](https://fate.technology/guide/core-concepts) to learn how to use _fate_ in your React applications.
18
+
19
+ ## Technologies
20
+
21
+ This default template combines the Nakazawa Tech's [Web App Template](https://github.com/nkzw-tech/web-app-template) and [Server Template](https://github.com/nkzw-tech/server-template) into a monorepo, with unified tooling and _fate_ as the data client. It currently uses GraphQL with Pothos, Relay-style connections, and Prisma.
22
+
23
+ _This template lives on the edge._ It's a turbocharged starting point on top of an already optimized stack. It uses TypeScript Go, Vite+, and ships with sensible defaults to unlock an incredibly fast development experience. It follows the principles laid out in [Building Scalable Applications](https://www.youtube.com/watch?v=rxPTEko8J7c&t=36s).
24
+
25
+ Next to [_fate_](https://fate.technology), it comes with the following technologies:
26
+
27
+ ### Client Technologies
28
+
29
+ - [Vite 8](https://vitejs.dev/)
30
+ - [React](https://reactjs.org/) with [React Compiler](https://react.dev/learn/react-compiler) enabled
31
+ - [Tailwind](https://tailwindcss.com/)
32
+ - [fbtee](https://github.com/nkzw-tech/fbtee) for i18n
33
+ - [Better Auth](https://www.better-auth.com/) for authentication
34
+ - [Void Pages Router](https://void.cloud/docs)
35
+ - [TypeScript](https://www.typescriptlang.org)
36
+ - [pnpm](https://pnpm.io/)
37
+
38
+ ### Server Technologies
39
+
40
+ - [Prisma](https://www.prisma.io/) as the ORM, with the new ESM multi-file generated client.
41
+ - [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server) and [Pothos](https://pothos-graphql.dev/) for the GraphQL API.
42
+ - [graphql-sse](https://github.com/enisdenjo/graphql-sse) for live updates.
43
+ - [Hono](https://hono.dev/)
44
+ - [Better Auth](https://better-auth.com/) for Authentication.
45
+
46
+ ### Folder Structure
47
+
48
+ - `client/` - The React client application using _fate_.
49
+ - `server/` - The fate server using GraphQL with Pothos and Prisma.
50
+
51
+ ## Initial Setup
52
+
53
+ You'll need Node.js 24+ and [Vite+](https://viteplus.dev/guide/).
54
+
55
+ Install dependencies:
56
+
57
+ ```bash
58
+ vp install
59
+ ```
60
+
61
+ Review `server/.env`, which is copied from `server/.env.example` when the app is created. The default local values expect:
62
+
63
+ - Postgres at `postgresql://fate:echo@localhost:5432/fate`.
64
+ - The server at `http://localhost:9000`.
65
+ - The client at `http://localhost:5173`.
66
+
67
+ Start Postgres with Docker:
68
+
69
+ ```bash
70
+ docker-compose up -d
71
+ ```
72
+
73
+ Alternatively, create the database manually:
74
+
75
+ ```SQL
76
+ CREATE ROLE fate WITH LOGIN PASSWORD 'echo';
77
+ CREATE DATABASE fate;
78
+ ALTER DATABASE fate OWNER TO fate;
79
+ ```
80
+
81
+ For local development, push the Prisma schema and seed the database:
82
+
83
+ ```bash
84
+ vp run prisma db push
85
+ vp run prisma db seed
86
+ ```
87
+
88
+ If you prefer migration files, use Prisma's migration workflow instead:
89
+
90
+ ```bash
91
+ vp run prisma migrate dev --name init
92
+ vp run prisma db seed
93
+ ```
94
+
95
+ Then generate local files:
96
+
97
+ ```bash
98
+ vp run dev:setup
99
+ ```
100
+
101
+ Start the app:
102
+
103
+ ```bash
104
+ vp run dev
105
+ ```
106
+
107
+ The client runs at `http://localhost:5173` and the server runs at `http://localhost:9000`. GraphQL requests go to `/graphql` and live updates use `/graphql/stream`.
108
+
109
+ ## Development
110
+
111
+ Common commands from the project root:
112
+
113
+ - `vp run dev` starts the client and server together.
114
+ - `vp run dev:client` starts only the client.
115
+ - `vp run dev:server` starts only the server.
116
+ - `vp run dev:setup` generates the Prisma client, Pothos schema import map, fbtee setup, and fate client support.
117
+ - `vp run fate:generate` refreshes fate client support after changing server views, roots, or routers.
118
+ - `vp run prisma` runs Prisma CLI commands in the server package.
119
+ - `vp check --fix` formats, lints, and type-checks the workspace.
120
+ - `vp test` runs the test suite.
121
+ - `vp run build` builds the client and server.