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.
- package/README.md +3 -0
- package/bin/create-fate.mjs +594 -9
- package/package.json +2 -2
- package/templates/fate/README.md +3 -0
- package/templates/fate/_shared/vue-app/package.json +54 -0
- package/templates/fate/_shared/vue-app/pages/index.vue +57 -0
- package/templates/fate/_shared/vue-app/pages/layout.vue +32 -0
- package/templates/fate/_shared/vue-app/pages/post/[id].vue +24 -0
- package/templates/fate/_shared/vue-app/src/App.css +63 -0
- package/templates/fate/_shared/vue-app/src/fateViews.ts +14 -0
- package/templates/fate/_shared/vue-app/src/lib/cx.ts +6 -0
- package/templates/fate/_shared/vue-app/src/lib/env.ts +3 -0
- package/templates/fate/_shared/vue-app/src/ui/Button.vue +67 -0
- package/templates/fate/_shared/vue-app/src/ui/Card.vue +16 -0
- package/templates/fate/_shared/vue-app/src/ui/Error.vue +9 -0
- package/templates/fate/_shared/vue-app/src/ui/ErrorBoundary.vue +22 -0
- package/templates/fate/_shared/vue-app/src/ui/Header.vue +35 -0
- package/templates/fate/_shared/vue-app/src/ui/Link.vue +15 -0
- package/templates/fate/_shared/vue-app/src/ui/PostCard.vue +29 -0
- package/templates/fate/_shared/vue-app/src/ui/Section.vue +19 -0
- package/templates/fate/_shared/vue-app/src/ui/Thinking.vue +11 -0
- package/templates/fate/_shared/vue-app/src/ui/UserCard.vue +23 -0
- package/templates/fate/_shared/vue-app/src/vue-shims.d.ts +6 -0
- package/templates/fate/_shared/vue-app/tsconfig.json +22 -0
- package/templates/fate/_shared/vue-app/vite.config.ts +58 -0
- package/templates/fate/_shared/vue-app/void.json +5 -0
- package/templates/fate/cloudflare/.github/workflows/push.yml +27 -0
- package/templates/fate/cloudflare/.vite-hooks/pre-commit +1 -0
- package/templates/fate/cloudflare/.vscode/extensions.json +12 -0
- package/templates/fate/cloudflare/.vscode/settings.json +25 -0
- package/templates/fate/cloudflare/AGENTS.md +55 -0
- package/templates/fate/cloudflare/README.md +126 -0
- package/templates/fate/cloudflare/_gitignore +20 -0
- package/templates/fate/cloudflare/client/package.json +56 -0
- package/templates/fate/cloudflare/client/pages/index.tsx +96 -0
- package/templates/fate/cloudflare/client/pages/layout.tsx +82 -0
- package/templates/fate/cloudflare/client/pages/login.tsx +107 -0
- package/templates/fate/cloudflare/client/pages/post/[id].tsx +22 -0
- package/templates/fate/cloudflare/client/pages/search.tsx +99 -0
- package/templates/fate/cloudflare/client/public/icon.svg +1 -0
- package/templates/fate/cloudflare/client/src/App.css +153 -0
- package/templates/fate/cloudflare/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/cloudflare/client/src/lib/cx.tsx +6 -0
- package/templates/fate/cloudflare/client/src/lib/env.tsx +5 -0
- package/templates/fate/cloudflare/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/cloudflare/client/src/ui/Button.tsx +108 -0
- package/templates/fate/cloudflare/client/src/ui/Card.tsx +32 -0
- package/templates/fate/cloudflare/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/cloudflare/client/src/ui/Error.tsx +19 -0
- package/templates/fate/cloudflare/client/src/ui/H2.tsx +15 -0
- package/templates/fate/cloudflare/client/src/ui/H3.tsx +20 -0
- package/templates/fate/cloudflare/client/src/ui/Header.tsx +98 -0
- package/templates/fate/cloudflare/client/src/ui/Input.tsx +17 -0
- package/templates/fate/cloudflare/client/src/ui/Link.tsx +11 -0
- package/templates/fate/cloudflare/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/cloudflare/client/src/ui/PostCard.tsx +303 -0
- package/templates/fate/cloudflare/client/src/ui/Section.tsx +23 -0
- package/templates/fate/cloudflare/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/cloudflare/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/cloudflare/client/translations/de_DE.json +689 -0
- package/templates/fate/cloudflare/client/translations/ja_JP.json +689 -0
- package/templates/fate/cloudflare/client/vite.config.ts +45 -0
- package/templates/fate/cloudflare/client/void.json +29 -0
- package/templates/fate/cloudflare/package.json +46 -0
- package/templates/fate/cloudflare/pnpm-workspace.yaml +39 -0
- package/templates/fate/cloudflare/server/.env.example +1 -0
- package/templates/fate/cloudflare/server/db/db.ts +30 -0
- package/templates/fate/cloudflare/server/db/migrations/20260508073357_initial_schema.sql +133 -0
- package/templates/fate/cloudflare/server/db/migrations/20260508120500_seed_cloudflare_demo.sql +170 -0
- package/templates/fate/cloudflare/server/db/migrations/meta/20260508073357_snapshot.json +868 -0
- package/templates/fate/cloudflare/server/db/migrations/meta/_journal.json +20 -0
- package/templates/fate/cloudflare/server/db/queries.ts +211 -0
- package/templates/fate/cloudflare/server/db/schema.ts +332 -0
- package/templates/fate/cloudflare/server/package.json +34 -0
- package/templates/fate/cloudflare/server/src/fate/commentPermissions.ts +17 -0
- package/templates/fate/cloudflare/server/src/fate/context.ts +15 -0
- package/templates/fate/cloudflare/server/src/fate/live.ts +7 -0
- package/templates/fate/cloudflare/server/src/fate/server.ts +411 -0
- package/templates/fate/cloudflare/server/src/fate/views.ts +164 -0
- package/templates/fate/cloudflare/server/src/index.ts +80 -0
- package/templates/fate/cloudflare/server/src/lib/auth.ts +66 -0
- package/templates/fate/cloudflare/server/src/lib/origins.ts +17 -0
- package/templates/fate/cloudflare/server/src/router.ts +2 -0
- package/templates/fate/cloudflare/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/cloudflare/server/tsconfig.json +10 -0
- package/templates/fate/cloudflare/server/wrangler.jsonc +29 -0
- package/templates/fate/cloudflare/tsconfig.json +33 -0
- package/templates/fate/cloudflare/vite.config.ts +72 -0
- package/templates/fate/drizzle/README.md +2 -8
- package/templates/fate/drizzle/client/pages/layout.tsx +2 -2
- package/templates/fate/drizzle/client/src/ui/PostCard.tsx +38 -15
- package/templates/fate/drizzle/pnpm-workspace.yaml +9 -3
- package/templates/fate/drizzle/server/vite.config.ts +1 -1
- package/templates/fate/graphql/.github/workflows/push.yml +27 -0
- package/templates/fate/graphql/.vite-hooks/pre-commit +1 -0
- package/templates/fate/graphql/.vscode/extensions.json +12 -0
- package/templates/fate/graphql/.vscode/settings.json +25 -0
- package/templates/fate/graphql/AGENTS.md +55 -0
- package/templates/fate/graphql/README.md +121 -0
- package/templates/fate/graphql/_gitignore +23 -0
- package/templates/fate/graphql/client/package.json +57 -0
- package/templates/fate/graphql/client/pages/index.tsx +96 -0
- package/templates/fate/graphql/client/pages/layout.tsx +83 -0
- package/templates/fate/graphql/client/pages/login.tsx +107 -0
- package/templates/fate/graphql/client/pages/post/[id].tsx +22 -0
- package/templates/fate/graphql/client/pages/search.tsx +99 -0
- package/templates/fate/graphql/client/public/icon.svg +1 -0
- package/templates/fate/graphql/client/src/App.css +153 -0
- package/templates/fate/graphql/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/graphql/client/src/lib/cx.tsx +6 -0
- package/templates/fate/graphql/client/src/lib/env.tsx +5 -0
- package/templates/fate/graphql/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/graphql/client/src/ui/Button.tsx +108 -0
- package/templates/fate/graphql/client/src/ui/Card.tsx +32 -0
- package/templates/fate/graphql/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/graphql/client/src/ui/Error.tsx +19 -0
- package/templates/fate/graphql/client/src/ui/H2.tsx +15 -0
- package/templates/fate/graphql/client/src/ui/H3.tsx +20 -0
- package/templates/fate/graphql/client/src/ui/Header.tsx +98 -0
- package/templates/fate/graphql/client/src/ui/Input.tsx +17 -0
- package/templates/fate/graphql/client/src/ui/Link.tsx +11 -0
- package/templates/fate/graphql/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/graphql/client/src/ui/PostCard.tsx +303 -0
- package/templates/fate/graphql/client/src/ui/Section.tsx +23 -0
- package/templates/fate/graphql/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/graphql/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/graphql/client/translations/de_DE.json +689 -0
- package/templates/fate/graphql/client/translations/ja_JP.json +689 -0
- package/templates/fate/graphql/client/vite.config.ts +40 -0
- package/templates/fate/graphql/client/void.json +29 -0
- package/templates/fate/graphql/docker-compose.yml +11 -0
- package/templates/fate/graphql/package.json +47 -0
- package/templates/fate/graphql/pnpm-workspace.yaml +45 -0
- package/templates/fate/graphql/server/.env.example +5 -0
- package/templates/fate/graphql/server/README.md +27 -0
- package/templates/fate/graphql/server/package.json +64 -0
- package/templates/fate/graphql/server/prisma.config.ts +17 -0
- package/templates/fate/graphql/server/scripts/generate-graphql.tsx +40 -0
- package/templates/fate/graphql/server/src/__tests__/index.test.tsx +5 -0
- package/templates/fate/graphql/server/src/app.tsx +47 -0
- package/templates/fate/graphql/server/src/graphql/builder.tsx +121 -0
- package/templates/fate/graphql/server/src/graphql/context.tsx +22 -0
- package/templates/fate/graphql/server/src/graphql/fate.ts +85 -0
- package/templates/fate/graphql/server/src/graphql/lib/authDirectives.tsx +93 -0
- package/templates/fate/graphql/server/src/graphql/lib/decodeGlobalID.tsx +12 -0
- package/templates/fate/graphql/server/src/graphql/lib/decodeIDOrThrow.tsx +10 -0
- package/templates/fate/graphql/server/src/graphql/lib/encodeGlobalID.tsx +5 -0
- package/templates/fate/graphql/server/src/graphql/live.tsx +241 -0
- package/templates/fate/graphql/server/src/graphql/mutations.tsx +265 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Category.tsx +35 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Comment.tsx +46 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Event.tsx +71 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Post.tsx +45 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Tag.tsx +11 -0
- package/templates/fate/graphql/server/src/graphql/nodes/User.tsx +28 -0
- package/templates/fate/graphql/server/src/graphql/schema.tsx +16 -0
- package/templates/fate/graphql/server/src/graphql/views.ts +193 -0
- package/templates/fate/graphql/server/src/index.tsx +45 -0
- package/templates/fate/graphql/server/src/lib/auth.tsx +31 -0
- package/templates/fate/graphql/server/src/lib/env.tsx +3 -0
- package/templates/fate/graphql/server/src/lib/origins.ts +18 -0
- package/templates/fate/graphql/server/src/prisma/prisma.tsx +9 -0
- package/templates/fate/graphql/server/src/prisma/schema.prisma +181 -0
- package/templates/fate/graphql/server/src/prisma/seed.tsx +161 -0
- package/templates/fate/graphql/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/graphql/server/src/user/isAdmin.tsx +3 -0
- package/templates/fate/graphql/server/tsconfig.json +33 -0
- package/templates/fate/graphql/server/vite.config.ts +55 -0
- package/templates/fate/graphql/tsconfig.json +33 -0
- package/templates/fate/graphql/vite.config.ts +80 -0
- package/templates/fate/graphql-client/.env.example +3 -0
- package/templates/fate/graphql-client/AGENTS.md +3 -0
- package/templates/fate/graphql-client/README.md +69 -0
- package/templates/fate/graphql-client/_gitignore +14 -0
- package/templates/fate/graphql-client/package.json +62 -0
- package/templates/fate/graphql-client/pages/index.tsx +69 -0
- package/templates/fate/graphql-client/pages/layout.tsx +41 -0
- package/templates/fate/graphql-client/pnpm-workspace.yaml +38 -0
- package/templates/fate/graphql-client/public/icon.svg +8 -0
- package/templates/fate/graphql-client/src/App.css +28 -0
- package/templates/fate/graphql-client/src/fate/graphql.ts +52 -0
- package/templates/fate/graphql-client/tsconfig.json +30 -0
- package/templates/fate/graphql-client/vite.config.ts +87 -0
- package/templates/fate/graphql-client/void.json +29 -0
- package/templates/fate/http/README.md +2 -8
- package/templates/fate/http/client/pages/layout.tsx +2 -2
- package/templates/fate/http/client/src/ui/PostCard.tsx +37 -15
- package/templates/fate/http/pnpm-workspace.yaml +9 -3
- package/templates/fate/http/server/vite.config.ts +1 -1
- package/templates/fate/prisma/README.md +2 -8
- package/templates/fate/prisma/client/pages/layout.tsx +2 -2
- package/templates/fate/prisma/client/src/ui/PostCard.tsx +37 -15
- package/templates/fate/prisma/pnpm-workspace.yaml +9 -3
- package/templates/fate/prisma/server/vite.config.ts +1 -1
- package/templates/fate/prisma/vite.config.ts +1 -1
- package/templates/fate/void/package.json +10 -2
- package/templates/fate/void/pages/login.tsx +3 -3
- package/templates/fate/void/pnpm-workspace.yaml +12 -4
- package/templates/fate/void/src/ui/Button.tsx +42 -26
- package/templates/fate/void/src/ui/CategoryCard.tsx +3 -7
- package/templates/fate/void/src/ui/PostCard.tsx +38 -15
- package/templates/fate/void/vite.config.ts +15 -14
- package/templates/fate/drizzle/public/fate-logo-dark.svg +0 -4
- package/templates/fate/drizzle/public/fate-logo.svg +0 -4
- package/templates/fate/http/public/fate-logo-dark.svg +0 -4
- package/templates/fate/http/public/fate-logo.svg +0 -4
- package/templates/fate/prisma/public/fate-logo-dark.svg +0 -4
- 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,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,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,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';
|