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,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <rect width="64" height="64" rx="14" fill="#111827" />
3
+ <path
4
+ d="M18 18h28v6H25v10h17v6H25v16h-7V18Z"
5
+ fill="#fff"
6
+ />
7
+ </svg>
8
+
@@ -0,0 +1,28 @@
1
+ @import 'tailwindcss';
2
+
3
+ body {
4
+ margin: 0;
5
+ background: #f8fafc;
6
+ color: #111827;
7
+ font-family:
8
+ Inter,
9
+ ui-sans-serif,
10
+ system-ui,
11
+ -apple-system,
12
+ BlinkMacSystemFont,
13
+ 'Segoe UI',
14
+ sans-serif;
15
+ }
16
+
17
+ button,
18
+ input,
19
+ textarea {
20
+ font: inherit;
21
+ }
22
+
23
+ @media (prefers-color-scheme: dark) {
24
+ body {
25
+ background: #0f172a;
26
+ color: #f8fafc;
27
+ }
28
+ }
@@ -0,0 +1,52 @@
1
+ import { graphqlMutation } from '@nkzw/fate';
2
+ import { dataView, list, type Entity } from '@nkzw/fate/server';
3
+
4
+ type GraphQLUser = {
5
+ id: string;
6
+ name?: string | null;
7
+ username?: string | null;
8
+ };
9
+
10
+ type GraphQLPost = {
11
+ author?: GraphQLUser | null;
12
+ id: string;
13
+ title: string;
14
+ };
15
+
16
+ export const userDataView = dataView<GraphQLUser>('User')({
17
+ id: true,
18
+ name: true,
19
+ username: true,
20
+ });
21
+
22
+ export const postDataView = dataView<GraphQLPost>('Post')({
23
+ author: userDataView,
24
+ id: true,
25
+ title: true,
26
+ });
27
+
28
+ export type User = Entity<typeof userDataView, 'User'>;
29
+ export type Post = Entity<
30
+ typeof postDataView,
31
+ 'Post',
32
+ {
33
+ author: User | null;
34
+ }
35
+ >;
36
+
37
+ export const Root = {
38
+ posts: list(postDataView),
39
+ viewer: userDataView,
40
+ };
41
+
42
+ export const fateGraphQL = {
43
+ mutations: {
44
+ 'post.like': graphqlMutation<Post, { id: string }, Post>('Post', {
45
+ field: 'postLike',
46
+ }),
47
+ },
48
+ roots: {
49
+ posts: { field: 'posts' },
50
+ viewer: { field: 'viewer' },
51
+ },
52
+ } as const;
@@ -0,0 +1,30 @@
1
+ {
2
+ "extends": "./.void/tsconfig.json",
3
+ "compilerOptions": {
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": "ESNext",
12
+ "moduleResolution": "Bundler",
13
+ "noEmit": true,
14
+ "noImplicitOverride": true,
15
+ "noUnusedLocals": true,
16
+ "resolveJsonModule": true,
17
+ "skipLibCheck": true,
18
+ "strict": true,
19
+ "target": "ES2022",
20
+ "types": ["vite/client", "void/env"]
21
+ },
22
+ "include": [
23
+ ".fate/**/*.ts",
24
+ ".void/**/*.d.ts",
25
+ "pages/**/*.ts",
26
+ "pages/**/*.tsx",
27
+ "src/**/*.ts",
28
+ "src/**/*.tsx"
29
+ ]
30
+ }
@@ -0,0 +1,87 @@
1
+ import nkzw from '@nkzw/oxlint-config';
2
+ import babel from '@rolldown/plugin-babel';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import { reactCompilerPreset } from '@vitejs/plugin-react';
5
+ import { voidReact } from '@void/react/plugin';
6
+ import { fate } from 'react-fate/vite';
7
+ import { defineConfig, lazyPlugins } from 'vite-plus';
8
+ import { voidPlugin } from 'void';
9
+
10
+ const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST;
11
+
12
+ export default defineConfig({
13
+ optimizeDeps: {
14
+ exclude: ['@nkzw/fate/client', 'react-fate/client'],
15
+ },
16
+ environments: {
17
+ void_worker: {
18
+ optimizeDeps: {
19
+ exclude: [
20
+ '@nkzw/fate',
21
+ '@nkzw/fate/client',
22
+ '@nkzw/fate/server',
23
+ '@void/react',
24
+ '@void/react/pages-server',
25
+ 'lucide-react',
26
+ 'react-error-boundary',
27
+ 'react-fate',
28
+ 'react-fate/client',
29
+ ],
30
+ },
31
+ },
32
+ },
33
+ fmt: {
34
+ experimentalSortImports: {
35
+ newlinesBetween: false,
36
+ },
37
+ experimentalSortPackageJson: {
38
+ sortScripts: true,
39
+ },
40
+ experimentalTailwindcss: {
41
+ stylesheet: 'src/App.css',
42
+ },
43
+ ignorePatterns: ['.fate/', '.void/', 'dist/', 'node_modules/', 'pnpm-lock.yaml'],
44
+ singleQuote: true,
45
+ },
46
+ lint: {
47
+ extends: [nkzw],
48
+ ignorePatterns: ['.fate', '.void', 'dist', 'node_modules', 'vite.config.ts.timestamp-*'],
49
+ options: { typeAware: true, typeCheck: true },
50
+ overrides: [
51
+ {
52
+ files: ['src/fate/graphql.ts'],
53
+ rules: {
54
+ 'no-console': 'off',
55
+ },
56
+ },
57
+ ],
58
+ rules: {
59
+ '@typescript-eslint/no-explicit-any': 'off',
60
+ },
61
+ },
62
+ plugins: [
63
+ ...(lazyPlugins(() => [
64
+ babel({
65
+ presets: [reactCompilerPreset()],
66
+ }),
67
+ tailwindcss(),
68
+ ...(isTest ? [] : [voidPlugin(), voidReact()]),
69
+ ]) ?? []),
70
+ fate({
71
+ module: './src/fate/graphql.ts',
72
+ transport: 'graphql',
73
+ }),
74
+ ],
75
+ run: {
76
+ tasks: {
77
+ 'test:all': {
78
+ command: 'vp check && vp test',
79
+ },
80
+ },
81
+ },
82
+ server: { port: 6001 },
83
+ ssr: { noExternal: ['@nkzw/fate', 'react-fate'] },
84
+ staged: {
85
+ '*': 'vp check --fix',
86
+ },
87
+ });
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "./node_modules/void/schema.json",
3
+ "head": {
4
+ "title": "fate",
5
+ "titleTemplate": "%s | fate",
6
+ "htmlAttrs": {
7
+ "lang": "en"
8
+ },
9
+ "meta": [
10
+ {
11
+ "charset": "utf-8"
12
+ },
13
+ {
14
+ "name": "viewport",
15
+ "content": "width=device-width, initial-scale=1"
16
+ }
17
+ ],
18
+ "link": [
19
+ {
20
+ "rel": "icon",
21
+ "href": "/icon.svg"
22
+ }
23
+ ]
24
+ },
25
+ "worker": {
26
+ "compatibility_date": "2025-12-01",
27
+ "compatibility_flags": ["nodejs_compat", "no_handle_cross_request_promise_resolution"]
28
+ }
29
+ }
@@ -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
 
@@ -36,7 +36,7 @@ const LocaleContext = createLocaleContext({
36
36
  });
37
37
 
38
38
  export default function Layout({ children }: { children: ReactNode }) {
39
- const { data: session, isPending } = AuthClient.useSession();
39
+ const { data: session } = AuthClient.useSession();
40
40
  const userId = session?.user.id;
41
41
 
42
42
  const fate = useMemo(
@@ -57,7 +57,7 @@ export default function Layout({ children }: { children: ReactNode }) {
57
57
  <div className="min-h-screen bg-background text-foreground">
58
58
  <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%)]">
59
59
  <Header />
60
- {isPending ? (
60
+ {session ? (
61
61
  <Thinking />
62
62
  ) : (
63
63
  <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,15 @@ import {
11
12
  useState,
12
13
  } from 'react';
13
14
  import { ErrorBoundary } from 'react-error-boundary';
14
- import { useFateClient, useListView, useView, view, ViewRef } from 'react-fate';
15
+ import {
16
+ defer,
17
+ type Deferred,
18
+ useListView,
19
+ useFateClient,
20
+ useView,
21
+ view,
22
+ ViewRef,
23
+ } from 'react-fate';
15
24
  import { Button } from '../ui/Button.tsx';
16
25
  import Card from '../ui/Card.tsx';
17
26
  import AuthClient from '../user/AuthClient.tsx';
@@ -30,13 +39,36 @@ const CommentConnectionView = {
30
39
  export const PostView = view<Post>()({
31
40
  author: UserView,
32
41
  commentCount: true,
33
- comments: CommentConnectionView,
42
+ comments: defer(CommentConnectionView),
34
43
  content: true,
35
44
  id: true,
36
45
  likes: true,
37
46
  title: true,
38
47
  });
39
48
 
49
+ const PostComments = ({
50
+ comments,
51
+ post,
52
+ }: {
53
+ comments: Deferred<{ items: ReadonlyArray<{ node: ViewRef<'Comment'> }> }>;
54
+ post: { commentCount: number; id: string; title: string };
55
+ }) => {
56
+ const [items, loadNext] = useListView(CommentConnectionView, comments);
57
+
58
+ return items.length > 0 ? (
59
+ <VStack gap={12}>
60
+ {items.map(({ node }) => (
61
+ <CommentCard comment={node} key={node.id} post={post} />
62
+ ))}
63
+ {loadNext ? (
64
+ <Button onClick={loadNext} variant="ghost">
65
+ <fbt desc="Load more comments button">Load more comments</fbt>
66
+ </Button>
67
+ ) : null}
68
+ </VStack>
69
+ ) : null;
70
+ };
71
+
40
72
  const CommentInput = ({
41
73
  error,
42
74
  post,
@@ -125,7 +157,6 @@ export function PostCard({ detail, post: postRef }: { detail?: boolean; post: Vi
125
157
  const fate = useFateClient();
126
158
  const post = useView(PostView, postRef);
127
159
  const author = useView(UserView, post.author);
128
- const [comments, loadNext] = useListView(CommentConnectionView, post.comments);
129
160
 
130
161
  const [likeResult, likeAction, likeIsPending] = useActionState(fate.actions.post.like, null);
131
162
 
@@ -259,18 +290,9 @@ export function PostCard({ detail, post: postRef }: { detail?: boolean; post: Vi
259
290
  </fbt:plural>
260
291
  </fbt>
261
292
  </h4>
262
- {comments.length > 0 ? (
263
- <VStack gap={12}>
264
- {comments.map(({ node }) => (
265
- <CommentCard comment={node} key={node.id} post={post} />
266
- ))}
267
- {loadNext ? (
268
- <Button onClick={loadNext} variant="ghost">
269
- <fbt desc="Load more comments button">Load more comments</fbt>
270
- </Button>
271
- ) : null}
272
- </VStack>
273
- ) : null}
293
+ <Suspense fallback={null}>
294
+ <PostComments comments={post.comments} post={post} />
295
+ </Suspense>
274
296
  <ErrorBoundary fallbackRender={({ error }) => <CommentInput error={error} post={post} />}>
275
297
  <CommentInput post={post} />
276
298
  </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: { file: 'dist/index.js' },
6
+ outputOptions: { codeSplitting: false, entryFileNames: 'index.js' },
7
7
  },
8
8
  });
@@ -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(
@@ -62,7 +62,7 @@ export default function Layout({ children }: { children: ReactNode }) {
62
62
  <div className="min-h-screen bg-background text-foreground">
63
63
  <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%)]">
64
64
  <Header />
65
- {isPending ? (
65
+ {session ? (
66
66
  <Thinking />
67
67
  ) : (
68
68
  <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,15 @@ import {
11
12
  useState,
12
13
  } from 'react';
13
14
  import { ErrorBoundary } from 'react-error-boundary';
14
- import { useFateClient, useListView, useView, view, ViewRef } from 'react-fate';
15
+ import {
16
+ defer,
17
+ type Deferred,
18
+ useListView,
19
+ useFateClient,
20
+ useView,
21
+ view,
22
+ ViewRef,
23
+ } from 'react-fate';
15
24
  import { Button } from '../ui/Button.tsx';
16
25
  import Card from '../ui/Card.tsx';
17
26
  import AuthClient from '../user/AuthClient.tsx';
@@ -30,13 +39,36 @@ const CommentConnectionView = {
30
39
  export const PostView = view<Post>()({
31
40
  author: UserView,
32
41
  commentCount: true,
33
- comments: CommentConnectionView,
42
+ comments: defer(CommentConnectionView),
34
43
  content: true,
35
44
  id: true,
36
45
  likes: true,
37
46
  title: true,
38
47
  });
39
48
 
49
+ const PostComments = ({
50
+ comments,
51
+ post,
52
+ }: {
53
+ comments: Deferred<{ items: ReadonlyArray<{ node: ViewRef<'Comment'> }> }>;
54
+ post: { commentCount: number; id: string; title: string };
55
+ }) => {
56
+ const [items, loadNext] = useListView(CommentConnectionView, comments);
57
+
58
+ return items.length > 0 ? (
59
+ <VStack gap={12}>
60
+ {items.map(({ node }) => (
61
+ <CommentCard comment={node} key={node.id} post={post} />
62
+ ))}
63
+ {loadNext ? (
64
+ <Button onClick={loadNext} variant="ghost">
65
+ <fbt desc="Load more comments button">Load more comments</fbt>
66
+ </Button>
67
+ ) : null}
68
+ </VStack>
69
+ ) : null;
70
+ };
71
+
40
72
  const CommentInput = ({
41
73
  error,
42
74
  post,
@@ -125,7 +157,6 @@ export function PostCard({ detail, post: postRef }: { detail?: boolean; post: Vi
125
157
  const fate = useFateClient();
126
158
  const post = useView(PostView, postRef);
127
159
  const author = useView(UserView, post.author);
128
- const [comments, loadNext] = useListView(CommentConnectionView, post.comments);
129
160
 
130
161
  const [likeResult, likeAction, likeIsPending] = useActionState(fate.actions.post.like, null);
131
162
 
@@ -259,18 +290,9 @@ export function PostCard({ detail, post: postRef }: { detail?: boolean; post: Vi
259
290
  </fbt:plural>
260
291
  </fbt>
261
292
  </h4>
262
- {comments.length > 0 ? (
263
- <VStack gap={12}>
264
- {comments.map(({ node }) => (
265
- <CommentCard comment={node} key={node.id} post={post} />
266
- ))}
267
- {loadNext ? (
268
- <Button onClick={loadNext} variant="ghost">
269
- <fbt desc="Load more comments button">Load more comments</fbt>
270
- </Button>
271
- ) : null}
272
- </VStack>
273
- ) : null}
293
+ <Suspense fallback={null}>
294
+ <PostComments comments={post.comments} post={post} />
295
+ </Suspense>
274
296
  <ErrorBoundary fallbackRender={({ error }) => <CommentInput error={error} post={post} />}>
275
297
  <CommentInput post={post} />
276
298
  </ErrorBoundary>
@@ -12,9 +12,14 @@ allowBuilds:
12
12
  workerd: true
13
13
 
14
14
  catalog:
15
- vite: npm:@voidzero-dev/vite-plus-core@^0.1.20
16
- vite-plus: ^0.1.20
17
- vitest: npm:@voidzero-dev/vite-plus-test@^0.1.20
15
+ vite: npm:@voidzero-dev/vite-plus-core@^0.1.23
16
+ vite-plus: ^0.1.23
17
+ vitest: npm:@voidzero-dev/vite-plus-test@^0.1.23
18
+
19
+ minimumReleaseAgeExclude:
20
+ - '@voidzero-dev/vite-plus-core'
21
+ - '@voidzero-dev/vite-plus-test'
22
+ - vite-plus
18
23
 
19
24
  onlyBuiltDependencies:
20
25
  - '@prisma/client'
@@ -27,6 +32,7 @@ onlyBuiltDependencies:
27
32
  - unrs-resolver
28
33
 
29
34
  overrides:
35
+ kysely: 0.28.17
30
36
  vite: 'catalog:'
31
37
  vitest: 'catalog:'
32
38
 
@@ -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: { file: 'dist/index.js' },
6
+ outputOptions: { codeSplitting: false, entryFileNames: 'index.js' },
7
7
  },
8
8
  });
@@ -41,7 +41,7 @@ export default defineConfig({
41
41
  'client/dist',
42
42
  'server/dist',
43
43
  'server/src/prisma/pothos-types.ts',
44
- 'server/src/prisma/prisma-client/*',
44
+ 'server/src/prisma/prisma-client/**',
45
45
  'vite.config.ts.timestamp-*',
46
46
  'client/vite.config.ts.timestamp-*',
47
47
  'server/vite.config.ts.timestamp-*',
@@ -17,7 +17,8 @@
17
17
  "dev": "vp dev",
18
18
  "dev:setup": "vp run prepare:void && vp run db:seed && vp run fate:generate",
19
19
  "fate:generate": "vp exec fate generate",
20
- "prepare:void": "void prepare"
20
+ "prepare:void": "void prepare",
21
+ "test:all": "vp check && vp test"
21
22
  },
22
23
  "dependencies": {
23
24
  "@nkzw/core": "^1.3.1",
@@ -40,17 +41,24 @@
40
41
  "zod": "^4.4.3"
41
42
  },
42
43
  "devDependencies": {
44
+ "@nkzw/eslint-plugin": "^2.0.0",
45
+ "@nkzw/oxlint-config": "^1.2.1",
43
46
  "@rolldown/plugin-babel": "^0.2.3",
44
47
  "@tailwindcss/vite": "^4.2.4",
45
48
  "@types/node": "^25.6.0",
46
49
  "@types/react": "^19.2.14",
47
50
  "@types/react-dom": "^19.2.3",
51
+ "@typescript/native-preview": "7.0.0-dev.20260505.1",
48
52
  "@vitejs/plugin-react": "^6.0.1",
49
53
  "babel-plugin-react-compiler": "^1.0.0",
54
+ "eslint-plugin-no-only-tests": "^3.4.0",
55
+ "eslint-plugin-perfectionist": "^5.9.0",
56
+ "eslint-plugin-react-hooks": "^7.1.1",
50
57
  "tailwindcss": "^4.2.4",
51
58
  "ts-node": "^10.9.2",
52
59
  "vite": "catalog:",
53
- "vite-plus": "catalog:"
60
+ "vite-plus": "catalog:",
61
+ "vitest": "catalog:"
54
62
  },
55
63
  "browserslist": [
56
64
  ">0.2%",
@@ -3,7 +3,7 @@ import { useRouter, useShared } from '@void/react';
3
3
  import { ExternalLinkIcon } from 'lucide-react';
4
4
  import { useActionState, useEffect, useState } from 'react';
5
5
  import type { SharedData } from '../src/lib/shared.ts';
6
- import { Button } from '../src/ui/Button.tsx';
6
+ import { AsyncButton } from '../src/ui/Button.tsx';
7
7
  import Card from '../src/ui/Card.tsx';
8
8
  import H2 from '../src/ui/H2.tsx';
9
9
  import Input from '../src/ui/Input.tsx';
@@ -71,9 +71,9 @@ export default function LoginPage() {
71
71
  value={password}
72
72
  />
73
73
  <div>
74
- <Button type="submit" variant="outline">
74
+ <AsyncButton type="submit" variant="outline">
75
75
  Sign In
76
- </Button>
76
+ </AsyncButton>
77
77
  </div>
78
78
  </VStack>
79
79
  </Stack>
@@ -2,18 +2,22 @@ packages:
2
2
  - .
3
3
 
4
4
  allowBuilds:
5
- '@swc/core': true
6
5
  better-sqlite3: true
7
6
  esbuild: true
8
7
  sharp: true
9
8
  workerd: true
10
9
 
11
10
  catalog:
12
- vite: npm:@voidzero-dev/vite-plus-core@0.1.20
13
- vite-plus: 0.1.20
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
14
19
 
15
20
  onlyBuiltDependencies:
16
- - '@swc/core'
17
21
  - '@tailwindcss/oxide'
18
22
  - better-sqlite3
19
23
  - esbuild
@@ -22,10 +26,14 @@ onlyBuiltDependencies:
22
26
  - workerd
23
27
 
24
28
  overrides:
29
+ kysely: 0.28.17
25
30
  vite: 'catalog:'
31
+ vitest: 'catalog:'
26
32
 
27
33
  peerDependencyRules:
28
34
  allowAny:
29
35
  - vite
36
+ - vitest
30
37
  allowedVersions:
31
38
  vite: '*'
39
+ vitest: '*'