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,40 @@
1
+ import { join } from 'node:path';
2
+ import fbteePreset from '@nkzw/babel-preset-fbtee';
3
+ import babel from '@rolldown/plugin-babel';
4
+ import tailwindcss from '@tailwindcss/vite';
5
+ import { reactCompilerPreset } from '@vitejs/plugin-react';
6
+ import { voidReact } from '@void/react/plugin';
7
+ import dotenv from 'dotenv';
8
+ import { fate } from 'react-fate/vite';
9
+ import { defineConfig, lazyPlugins } from 'vite-plus';
10
+ import { voidPlugin } from 'void';
11
+
12
+ const root = process.cwd();
13
+ const isDevelopment = process.env.NODE_ENV === 'development' || process.env.DEV;
14
+
15
+ dotenv.config({
16
+ path: join(root, '../server', isDevelopment ? '.env' : '.prod.env'),
17
+ quiet: true,
18
+ });
19
+
20
+ if (!process.env.VITE_SERVER_URL) {
21
+ throw new Error(`client-build, vite.config: 'VITE_SERVER_URL' is missing.`);
22
+ }
23
+
24
+ export default defineConfig({
25
+ build: { outDir: join(root, '../dist/client') },
26
+ plugins: [
27
+ ...(lazyPlugins(() => [
28
+ babel({
29
+ presets: [fbteePreset, reactCompilerPreset()],
30
+ }),
31
+ tailwindcss(),
32
+ voidPlugin(),
33
+ voidReact(),
34
+ ]) ?? []),
35
+ fate({
36
+ module: '@app/server/src/graphql/fate.ts',
37
+ transport: 'graphql',
38
+ }),
39
+ ],
40
+ });
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "./node_modules/void/schema.json",
3
+ "target": "node",
4
+ "head": {
5
+ "title": "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
+ "name": "theme-color",
19
+ "content": "#fff"
20
+ }
21
+ ],
22
+ "link": [
23
+ {
24
+ "rel": "icon",
25
+ "href": "/icon.svg"
26
+ }
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,11 @@
1
+ services:
2
+ db:
3
+ image: postgres:17
4
+ restart: always
5
+ container_name: fate-db
6
+ ports:
7
+ - '5432:5432'
8
+
9
+ environment:
10
+ POSTGRES_USER: fate
11
+ POSTGRES_PASSWORD: echo
@@ -0,0 +1,47 @@
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": "dotenv -e server/.env -- vp run --filter '@app/client' --filter '@app/server' build",
14
+ "dev": "npm-run-all --parallel dev:client dev:server",
15
+ "dev:client": "cd client && vp dev",
16
+ "dev:server": "cd server && vp run dev",
17
+ "dev:setup": "vp run --filter '@app/client' --filter '@app/server' dev:setup && vp run fate:generate",
18
+ "fate:generate": "vp run --filter '@app/client' fate:generate",
19
+ "prepare": "vp config",
20
+ "prisma": "vp run --filter '@app/server' prisma"
21
+ },
22
+ "devDependencies": {
23
+ "@app/server": "workspace:*",
24
+ "@nkzw/eslint-plugin": "^2.0.0",
25
+ "@nkzw/oxlint-config": "^1.2.1",
26
+ "@swc/core": "^1.15.33",
27
+ "@types/node": "^25.6.0",
28
+ "@typescript/native-preview": "7.0.0-dev.20260505.1",
29
+ "babel-plugin-react-compiler": "^1.0.0",
30
+ "dotenv": "^17.4.2",
31
+ "dotenv-cli": "^11.0.0",
32
+ "eslint-plugin-no-only-tests": "^3.4.0",
33
+ "eslint-plugin-perfectionist": "^5.9.0",
34
+ "eslint-plugin-react-hooks": "^7.1.1",
35
+ "npm-run-all2": "^8.0.4",
36
+ "prisma": "^7.8.0",
37
+ "ts-node": "^10.9.2",
38
+ "vite": "catalog:",
39
+ "vite-plus": "catalog:",
40
+ "vitest": "catalog:"
41
+ },
42
+ "engines": {
43
+ "node": ">=24.0.0",
44
+ "pnpm": ">=11.0.0"
45
+ },
46
+ "packageManager": "pnpm@11.0.6"
47
+ }
@@ -0,0 +1,45 @@
1
+ packages:
2
+ - client
3
+ - server
4
+
5
+ allowBuilds:
6
+ '@prisma/engines': true
7
+ '@swc/core': true
8
+ better-sqlite3: true
9
+ esbuild: true
10
+ prisma: true
11
+ sharp: true
12
+ workerd: true
13
+
14
+ catalog:
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
23
+
24
+ onlyBuiltDependencies:
25
+ - '@prisma/client'
26
+ - '@prisma/engines'
27
+ - '@swc/core'
28
+ - '@tailwindcss/oxide'
29
+ - esbuild
30
+ - msgpackr-extract
31
+ - prisma
32
+ - unrs-resolver
33
+
34
+ overrides:
35
+ kysely: 0.28.17
36
+ vite: 'catalog:'
37
+ vitest: 'catalog:'
38
+
39
+ peerDependencyRules:
40
+ allowAny:
41
+ - vite
42
+ - vitest
43
+ allowedVersions:
44
+ vite: '*'
45
+ vitest: '*'
@@ -0,0 +1,5 @@
1
+ BETTER_AUTH_SECRET="local-template-development-secret-with-enough-entropy"
2
+ BETTER_AUTH_URL="http://localhost:9000"
3
+ CLIENT_DOMAIN="http://localhost:5173"
4
+ DATABASE_URL="postgresql://fate:echo@localhost:5432/fate"
5
+ VITE_SERVER_URL="http://localhost:9000"
@@ -0,0 +1,27 @@
1
+ # Fate GraphQL Example
2
+
3
+ This server uses Pothos to expose a GraphQL API backed by Prisma.
4
+ It exposes Relay `node`/`nodes`, Relay connections for Fate list fields, GraphQL mutations
5
+ for the existing Fate mutations, and GraphQL SSE subscriptions for Fate live updates.
6
+
7
+ Run it from this directory:
8
+
9
+ ```sh
10
+ vp run dev:setup
11
+ vp run prisma migrate dev
12
+ vp run dev
13
+ ```
14
+
15
+ The server defaults to `http://localhost:9000/graphql`.
16
+
17
+ To generate the Fate client against this server, point the Vite plugin at:
18
+
19
+ ```ts
20
+ fate({
21
+ module: '@app/server/src/graphql/fate.ts',
22
+ transport: 'graphql',
23
+ });
24
+ ```
25
+
26
+ The generated client keeps the same Fate roots, views, mutations, store, and cache behavior;
27
+ only the transport and server module change.
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@app/server",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "author": "Christoph Nakazawa <christoph.pojer@gmail.com>",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git://github.com/nkzw-tech/fate.git"
9
+ },
10
+ "type": "module",
11
+ "scripts": {
12
+ "build": "vp pack",
13
+ "dev": "NODE_ENV=development node_modules/.bin/nodemon -q -I --exec node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm --env-file .env src/index.tsx",
14
+ "dev:setup": "pnpm prisma generate && pnpm generate-graphql",
15
+ "generate-graphql": "node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm ./scripts/generate-graphql.tsx",
16
+ "prisma": "prisma"
17
+ },
18
+ "dependencies": {
19
+ "@graphql-tools/utils": "^11.1.0",
20
+ "@graphql-yoga/plugin-graphql-sse": "^3.21.0",
21
+ "@hono/node-server": "^2.0.1",
22
+ "@nkzw/core": "^1.3.1",
23
+ "@nkzw/define-env": "^1.1.0",
24
+ "@nkzw/fate": "latest",
25
+ "@pothos/core": "^4.12.0",
26
+ "@pothos/plugin-complexity": "^4.1.2",
27
+ "@pothos/plugin-directives": "^4.3.0",
28
+ "@pothos/plugin-prisma": "^4.14.3",
29
+ "@pothos/plugin-relay": "^4.7.0",
30
+ "@pothos/plugin-scope-auth": "^4.1.6",
31
+ "@prisma/adapter-pg": "^7.8.0",
32
+ "@prisma/client": "^7.8.0",
33
+ "better-auth": "^1.6.9",
34
+ "graphql": "^16.13.2",
35
+ "graphql-yoga": "^5.21.0",
36
+ "hono": "^4.12.17"
37
+ },
38
+ "devDependencies": {
39
+ "@nkzw/eslint-plugin": "^2.0.0",
40
+ "@nkzw/oxlint-config": "^1.2.1",
41
+ "@swc/core": "^1.15.33",
42
+ "@types/node": "^25.6.0",
43
+ "@typescript/native-preview": "7.0.0-dev.20260505.1",
44
+ "dotenv": "^17.4.2",
45
+ "eslint-plugin-no-only-tests": "^3.4.0",
46
+ "eslint-plugin-perfectionist": "^5.9.0",
47
+ "eslint-plugin-react-hooks": "^7.1.1",
48
+ "glob": "^13.0.6",
49
+ "nodemon": "^3.1.14",
50
+ "oxfmt": "^0.48.0",
51
+ "prisma": "^7.8.0",
52
+ "prisma-json-types-generator": "^5.0.0",
53
+ "ts-node": "^10.9.2",
54
+ "typescript": "^6.0.3",
55
+ "vite-plus": "catalog:",
56
+ "vitest": "catalog:"
57
+ },
58
+ "nodemonConfig": {
59
+ "ext": "tsx",
60
+ "watch": [
61
+ "src/"
62
+ ]
63
+ }
64
+ }
@@ -0,0 +1,17 @@
1
+ import { join } from 'node:path';
2
+ import dotenv from 'dotenv';
3
+ import { defineConfig } from 'prisma/config';
4
+
5
+ const root = process.cwd();
6
+
7
+ dotenv.config({
8
+ path: join(root, '.env'),
9
+ quiet: true,
10
+ });
11
+
12
+ export default defineConfig({
13
+ migrations: {
14
+ seed: `node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm --env-file .env src/prisma/seed.tsx`,
15
+ },
16
+ schema: './src/prisma/schema.prisma',
17
+ });
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm
2
+ import { createHash } from 'node:crypto';
3
+ import { writeFileSync } from 'node:fs';
4
+ import { join, posix, relative, sep } from 'node:path';
5
+ import { styleText } from 'node:util';
6
+ import { globSync } from 'glob';
7
+ import { format } from 'oxfmt';
8
+
9
+ console.log(styleText('bold', '› Generating GraphQL schema import map...'));
10
+
11
+ const sign = (code: string) =>
12
+ `/* @generated(${createHash('sha256').update(code).digest('hex')}) */\n${code}`;
13
+
14
+ const root = process.cwd();
15
+ const path = join(root, 'src/graphql');
16
+ const outputFile = join(path, 'schemaImportMap.tsx');
17
+
18
+ const files = globSync([
19
+ `${path}/{nodes,mutations}/*.tsx`.split(sep).join(posix.sep),
20
+ `${path}/{live,mutations}.tsx`.split(sep).join(posix.sep),
21
+ ])
22
+ .map((file) => relative(path, file.slice(0, file.lastIndexOf('.'))))
23
+ .sort((a, b) => String(a).localeCompare(String(b)));
24
+
25
+ if (!files.length) {
26
+ throw new Error(`generate-graphql: No GraphQL schema files found.`);
27
+ }
28
+
29
+ writeFileSync(
30
+ outputFile,
31
+ sign(
32
+ (
33
+ await format(outputFile, `${files.map((name) => `import './${name}.tsx';`).join('\n')}`, {
34
+ singleQuote: true,
35
+ })
36
+ ).code,
37
+ ),
38
+ );
39
+
40
+ console.log(styleText(['green', 'bold'], '✓ Done generating GraphQL schema import map.'));
@@ -0,0 +1,5 @@
1
+ import { expect, test } from 'vite-plus/test';
2
+
3
+ test('App', () => {
4
+ expect(1 + 1).toBe(2);
5
+ });
@@ -0,0 +1,47 @@
1
+ import { styleText } from 'node:util';
2
+ import { useGraphQLSSE as createGraphQLSSEPlugin } from '@graphql-yoga/plugin-graphql-sse';
3
+ import { createYoga } from 'graphql-yoga';
4
+ import { Hono } from 'hono';
5
+ import { cors } from 'hono/cors';
6
+ import { Context, createContext } from './graphql/context.tsx';
7
+ import schema from './graphql/schema.tsx';
8
+ import { auth } from './lib/auth.tsx';
9
+ import { clientOrigin, resolveCorsOrigin } from './lib/origins.ts';
10
+ import prisma from './prisma/prisma.tsx';
11
+
12
+ try {
13
+ await prisma.$connect();
14
+ } catch (error) {
15
+ console.error(`${styleText(['red', 'bold'], 'Prisma Database Connection Error')}\n`, error);
16
+ process.exit(1);
17
+ }
18
+
19
+ const app = new Hono();
20
+
21
+ app.use(
22
+ cors({
23
+ credentials: true,
24
+ origin: resolveCorsOrigin,
25
+ }),
26
+ );
27
+
28
+ app.on(['POST', 'GET'], '/api/auth/*', ({ req }) => auth.handler(req.raw));
29
+
30
+ const yoga = createYoga<Context>({
31
+ graphiql: process.env.NODE_ENV === 'development',
32
+ plugins: [
33
+ createGraphQLSSEPlugin({
34
+ endpoint: '/graphql/stream',
35
+ }),
36
+ ],
37
+ schema,
38
+ });
39
+
40
+ app.on(['POST', 'GET', 'OPTIONS', 'DELETE', 'PUT'], '/graphql/*', async (context) => {
41
+ const req = context.req.raw;
42
+ return yoga.handleRequest(req, await createContext({ headers: req.headers }));
43
+ });
44
+
45
+ app.all('/*', (context) => context.redirect(clientOrigin));
46
+
47
+ export default app;
@@ -0,0 +1,121 @@
1
+ import SchemaBuilder from '@pothos/core';
2
+ import ComplexityPlugin from '@pothos/plugin-complexity';
3
+ import DirectivesPlugin from '@pothos/plugin-directives';
4
+ import PrismaPlugin from '@pothos/plugin-prisma';
5
+ import RelayPlugin from '@pothos/plugin-relay';
6
+ import ScopeAuthPlugin from '@pothos/plugin-scope-auth';
7
+ import { Kind, type ValueNode } from 'graphql';
8
+ import PrismaTypes, { getDatamodel } from '../prisma/pothos-types.ts';
9
+ import prisma from '../prisma/prisma.tsx';
10
+ import isAdmin from '../user/isAdmin.tsx';
11
+ import { Context } from './context.tsx';
12
+ import AuthDirectivesPlugin from './lib/authDirectives.tsx';
13
+ import decodeGlobalID from './lib/decodeGlobalID.tsx';
14
+ import encodeGlobalID from './lib/encodeGlobalID.tsx';
15
+
16
+ interface PothosTypes extends Partial<PothosSchemaTypes.UserSchemaTypes> {
17
+ AuthScopes: {
18
+ role: string;
19
+ self: string;
20
+ };
21
+ Context: Context;
22
+ PrismaTypes: PrismaTypes;
23
+ Scalars: {
24
+ DateTime: {
25
+ Input: Date | string;
26
+ Output: Date | string;
27
+ };
28
+ JSON: {
29
+ Input: unknown;
30
+ Output: unknown;
31
+ };
32
+ };
33
+ }
34
+
35
+ const builder = new SchemaBuilder<PothosTypes>({
36
+ complexity: {
37
+ defaultComplexity: 1,
38
+ defaultListMultiplier: 10,
39
+ limit: {
40
+ breadth: 300,
41
+ complexity: 20_000,
42
+ depth: 20,
43
+ },
44
+ },
45
+ directives: {
46
+ useGraphQLToolsUnorderedDirectives: true,
47
+ },
48
+ plugins: [
49
+ ScopeAuthPlugin,
50
+ ComplexityPlugin,
51
+ PrismaPlugin,
52
+ RelayPlugin,
53
+ AuthDirectivesPlugin,
54
+ DirectivesPlugin,
55
+ ],
56
+ prisma: {
57
+ client: prisma,
58
+ dmmf: getDatamodel(),
59
+ exposeDescriptions: false,
60
+ filterConnectionTotalCount: true,
61
+ maxConnectionSize: 120,
62
+ onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'error',
63
+ },
64
+ relay: {
65
+ clientMutationId: 'omit',
66
+ cursorType: 'String',
67
+ decodeGlobalID,
68
+ encodeGlobalID: (typename, id) => encodeGlobalID(typename as keyof PrismaTypes, id),
69
+ },
70
+ scopeAuth: {
71
+ authScopes: ({ sessionUser }) => ({
72
+ role: (role) =>
73
+ !!sessionUser && (sessionUser.role.split(',').includes(role) || isAdmin(sessionUser)),
74
+ self: (id) => !!sessionUser && id === sessionUser.id,
75
+ }),
76
+ },
77
+ });
78
+
79
+ builder.mutationType();
80
+ builder.queryType();
81
+ builder.subscriptionType();
82
+
83
+ const parseJSONLiteral = (ast: ValueNode): unknown => {
84
+ switch (ast.kind) {
85
+ case Kind.BOOLEAN:
86
+ return ast.value;
87
+ case Kind.FLOAT:
88
+ case Kind.INT:
89
+ return Number(ast.value);
90
+ case Kind.LIST:
91
+ return ast.values.map(parseJSONLiteral);
92
+ case Kind.NULL:
93
+ return null;
94
+ case Kind.OBJECT:
95
+ return Object.fromEntries(
96
+ ast.fields.map((field) => [field.name.value, parseJSONLiteral(field.value)]),
97
+ );
98
+ case Kind.STRING:
99
+ case Kind.ENUM:
100
+ return ast.value;
101
+ default:
102
+ return null;
103
+ }
104
+ };
105
+
106
+ const isDate = (value: unknown): value is Date =>
107
+ Object.prototype.toString.call(value) === '[object Date]';
108
+
109
+ export const DateTimeScalar = builder.scalarType('DateTime', {
110
+ parseLiteral: (ast) => (ast.kind === Kind.STRING ? ast.value : ''),
111
+ parseValue: (value) => (typeof value === 'string' || isDate(value) ? value : String(value)),
112
+ serialize: (value) => (isDate(value) ? value.toISOString() : value),
113
+ });
114
+
115
+ export const JSONScalar = builder.scalarType('JSON', {
116
+ parseLiteral: parseJSONLiteral,
117
+ parseValue: (value) => value,
118
+ serialize: (value) => value,
119
+ });
120
+
121
+ export default builder;
@@ -0,0 +1,22 @@
1
+ import { auth } from '../lib/auth.tsx';
2
+ import prisma from '../prisma/prisma.tsx';
3
+ import { toSessionUser, type SessionUser } from '../user/SessionUser.tsx';
4
+
5
+ type CreateContextOptions = {
6
+ headers?: Headers;
7
+ };
8
+
9
+ export const createContext = async (options?: CreateContextOptions) => {
10
+ const session = options?.headers ? await auth.api.getSession({ headers: options.headers }) : null;
11
+
12
+ return {
13
+ headers: options?.headers ?? new Headers(),
14
+ prisma,
15
+ sessionUser: session?.user ? toSessionUser(session.user) : null,
16
+ };
17
+ };
18
+
19
+ export type Context = Awaited<ReturnType<typeof createContext>>;
20
+ export type AppContext = Context & {
21
+ sessionUser: SessionUser | null;
22
+ };
@@ -0,0 +1,85 @@
1
+ import { graphqlMutation } from '@nkzw/fate';
2
+ import { createLiveEventBus } from '@nkzw/fate/server';
3
+ import { createPrismaSourceAdapter } from '@nkzw/fate/server/prisma';
4
+ import type { AppContext } from './context.tsx';
5
+ import {
6
+ categoryDataView,
7
+ commentDataView,
8
+ eventAttendeeDataView,
9
+ eventDataView,
10
+ postDataView,
11
+ Root,
12
+ tagDataView,
13
+ userDataView,
14
+ type Comment,
15
+ type Post,
16
+ type User,
17
+ } from './views.ts';
18
+
19
+ type FateMutationInput<Input extends Record<string, unknown>> = Input & {
20
+ args?: Record<string, unknown>;
21
+ };
22
+
23
+ export type PostAddInput = FateMutationInput<{
24
+ content: string;
25
+ title: string;
26
+ }>;
27
+ export type PostLikeInput = FateMutationInput<{
28
+ error?: 'boundary' | 'callSite';
29
+ id: string;
30
+ slow?: boolean;
31
+ }>;
32
+ export type PostUnlikeInput = FateMutationInput<{
33
+ id: string;
34
+ }>;
35
+ export type CommentAddInput = FateMutationInput<{
36
+ content: string;
37
+ postId: string;
38
+ }>;
39
+ export type CommentDeleteInput = FateMutationInput<{
40
+ id: string;
41
+ }>;
42
+ export type UserUpdateInput = FateMutationInput<{
43
+ name: string;
44
+ }>;
45
+
46
+ export const live = createLiveEventBus();
47
+
48
+ export const fate = createPrismaSourceAdapter<AppContext>({
49
+ views: Root,
50
+ });
51
+
52
+ export const viewsByType = {
53
+ Category: categoryDataView,
54
+ Comment: commentDataView,
55
+ Event: eventDataView,
56
+ EventAttendee: eventAttendeeDataView,
57
+ Post: postDataView,
58
+ Tag: tagDataView,
59
+ User: userDataView,
60
+ } as const;
61
+
62
+ export const fateGraphQL = {
63
+ mutations: {
64
+ 'comment.add': graphqlMutation<Comment, CommentAddInput, Comment>('Comment', {
65
+ field: 'commentAdd',
66
+ }),
67
+ 'comment.delete': graphqlMutation<Comment, CommentDeleteInput, Comment>('Comment', {
68
+ field: 'commentDelete',
69
+ }),
70
+ 'post.add': graphqlMutation<Post, PostAddInput, Post>('Post', {
71
+ field: 'postAdd',
72
+ }),
73
+ 'post.like': graphqlMutation<Post, PostLikeInput, Post>('Post', {
74
+ field: 'postLike',
75
+ }),
76
+ 'post.unlike': graphqlMutation<Post, PostUnlikeInput, Post>('Post', {
77
+ field: 'postUnlike',
78
+ }),
79
+ 'user.update': graphqlMutation<User, UserUpdateInput, User>('User', {
80
+ field: 'userUpdate',
81
+ }),
82
+ },
83
+ } as const;
84
+
85
+ export * from './views.ts';