@oyolincc/create 0.0.0-dev-20260918103756 → 0.0.0-dev-20261006065712
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/package.json +1 -1
- package/templates/hono-fullstack/.vscode/launch.json +14 -3
- package/templates/hono-fullstack/.vscode/settings.json +3 -1
- package/templates/hono-fullstack/AGENTS.md +7 -0
- package/templates/hono-fullstack/README.md +28 -14
- package/templates/hono-fullstack/_gitignore +4 -0
- package/templates/hono-fullstack/apps/server/.dev.vars.example +9 -0
- package/templates/hono-fullstack/apps/server/package.json +17 -10
- package/templates/hono-fullstack/apps/server/src/adapters/cloudflare/entry.ts +36 -0
- package/templates/hono-fullstack/apps/server/src/adapters/nodejs/bootstrap.ts +3 -6
- package/templates/hono-fullstack/apps/server/src/adapters/nodejs/entry.ts +21 -6
- package/templates/hono-fullstack/apps/server/src/app.ts +4 -6
- package/templates/hono-fullstack/apps/server/src/config.ts +47 -0
- package/templates/hono-fullstack/apps/server/src/infra/logger/pino.ts +4 -0
- package/templates/hono-fullstack/apps/server/src/infra/work-runner/example/worker-entry.ts +4 -25
- package/templates/hono-fullstack/apps/server/src/infra/work-runner/pass-through/runner.ts +42 -0
- package/templates/{hono-server/apps/server/src/infra/work-runner/example → hono-fullstack/apps/server/src/infra/work-runner/piscina}/runner.ts +9 -9
- package/templates/hono-fullstack/apps/server/src/infra/work-runner/types.ts +7 -2
- package/templates/hono-fullstack/apps/server/src/infra/work-runner/utils/message.ts +7 -4
- package/templates/hono-fullstack/apps/server/src/modules/example/routes.ts +3 -5
- package/templates/hono-fullstack/apps/server/src/modules/example/work-task/tasks.test.ts +17 -5
- package/templates/hono-fullstack/apps/server/src/modules/example/work-task/tasks.ts +38 -15
- package/templates/hono-fullstack/apps/server/src/shared/app/types.ts +8 -4
- package/templates/hono-fullstack/apps/server/src/shared/network/validator.ts +10 -3
- package/templates/hono-fullstack/apps/server/tsconfig.json +1 -0
- package/templates/hono-fullstack/apps/server/vite.config.ts +8 -3
- package/templates/hono-fullstack/apps/server/wrangler.jsonc +29 -0
- package/templates/hono-fullstack/apps/stack/backend/cloudflare.ts +5 -0
- package/templates/hono-fullstack/apps/stack/backend/node.ts +3 -0
- package/templates/hono-fullstack/apps/stack/frontend/components/general/ErrorMessage.tsx +4 -0
- package/templates/hono-fullstack/apps/stack/frontend/components/layouts/AppLayout.tsx +11 -0
- package/templates/hono-fullstack/apps/stack/frontend/components/layouts/Header.tsx +7 -0
- package/templates/hono-fullstack/apps/stack/frontend/components/layouts/Page.tsx +5 -0
- package/templates/hono-fullstack/apps/stack/frontend/components/layouts/PageContainer.tsx +5 -0
- package/templates/hono-fullstack/apps/stack/frontend/modules/example/components/ExampleForm.tsx +45 -0
- package/templates/hono-fullstack/apps/stack/frontend/modules/example/components/ExampleList.tsx +13 -0
- package/templates/hono-fullstack/apps/stack/frontend/modules/example/{hooks.ts → hooks/api-hooks.ts} +2 -1
- package/templates/hono-fullstack/apps/stack/frontend/modules/example/stores/index.ts +3 -0
- package/templates/hono-fullstack/apps/stack/frontend/routeTree.gen.ts +21 -4
- package/templates/hono-fullstack/apps/stack/frontend/routes/api/$.ts +18 -0
- package/templates/hono-fullstack/apps/stack/frontend/routes/index.tsx +48 -2
- package/templates/hono-fullstack/apps/stack/frontend/vite-env.d.ts +5 -0
- package/templates/hono-fullstack/apps/stack/package.json +20 -4
- package/templates/hono-fullstack/apps/stack/tsconfig.json +7 -4
- package/templates/hono-fullstack/apps/stack/tsr.config.json +2 -2
- package/templates/hono-fullstack/apps/stack/vite.config.ts +75 -26
- package/templates/hono-fullstack/apps/stack/wrangler.jsonc +20 -0
- package/templates/hono-fullstack/envs/.env.example.server +1 -1
- package/templates/hono-fullstack/envs/.env.example.sqlite +1 -2
- package/templates/hono-fullstack/envs/.env.example.sqlite-d1 +3 -0
- package/templates/hono-fullstack/libs/db/drizzle-kit/sqlite-d1.config.ts +22 -0
- package/templates/hono-fullstack/libs/db/package.json +9 -6
- package/templates/hono-fullstack/libs/db/src/dialects/sqlite/d1-client.ts +12 -0
- package/templates/hono-fullstack/libs/db/src/dialects/sqlite/schema/columns.ts +5 -0
- package/templates/hono-fullstack/libs/db/src/dialects/sqlite/schema/modules/examples/examples.ts +2 -2
- package/templates/hono-fullstack/libs/db/vite.config.ts +1 -0
- package/templates/hono-fullstack/libs/definition/src/configs/defs/runtime.ts +1 -0
- package/templates/hono-fullstack/libs/definition/src/configs/defs/sqlite.ts +16 -3
- package/templates/hono-fullstack/oyo.config.json +2 -1
- package/templates/hono-fullstack/package.json +1 -5
- package/templates/hono-fullstack/pnpm-workspace.yaml +8 -6
- package/templates/hono-fullstack/turbo.json +26 -3
- package/templates/hono-fullstack/vite.config.ts +1 -0
- package/templates/hono-server/.vscode/launch.json +7 -3
- package/templates/hono-server/.vscode/settings.json +3 -1
- package/templates/hono-server/AGENTS.md +7 -0
- package/templates/hono-server/README.md +25 -12
- package/templates/hono-server/_gitignore +4 -0
- package/templates/hono-server/apps/server/.dev.vars.example +9 -0
- package/templates/hono-server/apps/server/package.json +18 -9
- package/templates/hono-server/apps/server/src/adapters/cloudflare/entry.ts +36 -0
- package/templates/hono-server/apps/server/src/adapters/nodejs/bootstrap.ts +3 -3
- package/templates/hono-server/apps/server/src/adapters/nodejs/entry.ts +28 -6
- package/templates/hono-server/apps/server/src/app.ts +4 -6
- package/templates/hono-server/apps/server/src/config.ts +47 -0
- package/templates/hono-server/apps/server/src/infra/logger/pino.ts +4 -0
- package/templates/hono-server/apps/server/src/infra/work-runner/example/worker-entry.ts +4 -25
- package/templates/hono-server/apps/server/src/infra/work-runner/pass-through/runner.ts +42 -0
- package/templates/{hono-fullstack/apps/server/src/infra/work-runner/example → hono-server/apps/server/src/infra/work-runner/piscina}/runner.ts +9 -9
- package/templates/hono-server/apps/server/src/infra/work-runner/types.ts +7 -2
- package/templates/hono-server/apps/server/src/infra/work-runner/utils/message.ts +7 -4
- package/templates/hono-server/apps/server/src/modules/example/contracts.ts +4 -0
- package/templates/hono-server/apps/server/src/modules/example/routes.ts +32 -5
- package/templates/hono-server/apps/server/src/modules/example/work-task/tasks.test.ts +17 -5
- package/templates/hono-server/apps/server/src/modules/example/work-task/tasks.ts +38 -15
- package/templates/hono-server/apps/server/src/shared/app/types.ts +8 -4
- package/templates/hono-server/apps/server/src/shared/network/validator.ts +10 -3
- package/templates/hono-server/apps/server/tsconfig.json +1 -0
- package/templates/hono-server/apps/server/vite.config.ts +8 -3
- package/templates/hono-server/apps/server/wrangler.jsonc +29 -0
- package/templates/hono-server/docker/server.Dockerfile +3 -5
- package/templates/hono-server/envs/.env.example.server +1 -1
- package/templates/hono-server/envs/.env.example.sqlite +1 -0
- package/templates/hono-server/envs/.env.example.sqlite-d1 +3 -0
- package/templates/hono-server/libs/db/drizzle-kit/sqlite-d1.config.ts +22 -0
- package/templates/hono-server/libs/db/drizzle-kit/sqlite.config.ts +22 -0
- package/templates/hono-server/libs/db/package.json +42 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/client.ts +35 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/d1-client.ts +12 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/schema/columns.ts +37 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/schema/index.ts +7 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/schema/modules/examples/examples.ts +22 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/schema/relations.ts +5 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/schema/tables.ts +5 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/schema/types.ts +7 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/schema/vo.ts +1 -0
- package/templates/hono-server/libs/db/src/dialects/sqlite/utils.ts +8 -0
- package/templates/hono-server/libs/db/src/shared/types.ts +26 -0
- package/templates/hono-server/libs/db/tsconfig.json +10 -0
- package/templates/hono-server/libs/db/vite.config.ts +22 -0
- package/templates/hono-server/libs/definition/src/configs/defs/runtime.ts +1 -0
- package/templates/hono-server/libs/definition/src/configs/defs/sqlite.ts +26 -0
- package/templates/hono-server/libs/definition/src/configs/index.ts +1 -0
- package/templates/hono-server/libs/definition/vite.config.ts +1 -1
- package/templates/hono-server/oyo.config.json +3 -1
- package/templates/hono-server/package.json +2 -3
- package/templates/hono-server/pnpm-workspace.yaml +13 -4
- package/templates/hono-server/turbo.json +26 -3
- package/templates/hono-server/vite.config.ts +1 -0
- package/templates/monorepo/.vscode/launch.json +0 -3
- package/templates/monorepo/.vscode/settings.json +3 -1
- package/templates/monorepo/AGENTS.md +7 -0
- package/templates/monorepo/_gitignore +4 -0
- package/templates/monorepo/pnpm-workspace.yaml +6 -3
- package/templates/monorepo/turbo.json +26 -3
- package/templates/monorepo/vite.config.ts +1 -0
- package/templates/hono-fullstack/apps/server/src/adapters/nodejs/config.ts +0 -53
- package/templates/hono-fullstack/apps/server/src/app.test.ts +0 -52
- package/templates/hono-fullstack/apps/server/src/infra/work-runner/example/runner.test.ts +0 -56
- package/templates/hono-fullstack/apps/stack/frontend/modules/example/ExamplePage.tsx +0 -57
- package/templates/hono-fullstack/apps/stack/nitro/handlers/hono.ts +0 -1
- package/templates/hono-fullstack/apps/stack/nitro/plugins/hono.ts +0 -9
- package/templates/hono-server/apps/server/src/adapters/nodejs/config.ts +0 -53
- package/templates/hono-server/apps/server/src/app.test.ts +0 -52
- package/templates/hono-server/apps/server/src/infra/work-runner/example/runner.test.ts +0 -56
package/package.json
CHANGED
|
@@ -10,6 +10,20 @@
|
|
|
10
10
|
"sourceMaps": true,
|
|
11
11
|
"type": "node-terminal"
|
|
12
12
|
},
|
|
13
|
+
{
|
|
14
|
+
"command": "pnpm -C apps/server dev:node-chain",
|
|
15
|
+
"name": "repo-server",
|
|
16
|
+
"request": "launch",
|
|
17
|
+
"sourceMaps": true,
|
|
18
|
+
"type": "node-terminal"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"command": "pnpm -C apps/stack dev:node",
|
|
22
|
+
"name": "repo-stack",
|
|
23
|
+
"request": "launch",
|
|
24
|
+
"sourceMaps": true,
|
|
25
|
+
"type": "node-terminal"
|
|
26
|
+
},
|
|
13
27
|
{
|
|
14
28
|
"name": "Attach to process",
|
|
15
29
|
"port": 19230,
|
|
@@ -18,8 +32,5 @@
|
|
|
18
32
|
"type": "node"
|
|
19
33
|
}
|
|
20
34
|
],
|
|
21
|
-
// Use IntelliSense to learn about possible attributes.
|
|
22
|
-
// Hover to view descriptions of existing attributes.
|
|
23
|
-
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
24
35
|
"version": "0.2.0"
|
|
25
36
|
}
|
|
@@ -10,6 +10,13 @@ Docs are local at `node_modules/vite-plus/docs` or online at https://viteplus.de
|
|
|
10
10
|
|
|
11
11
|
`vp <name>` runs a built-in command. `vp run <name>` runs a `package.json` script or a `vite.config.ts` task. Scripts cannot overwrite built-ins, so `vp dev` and `vp run dev` may do different things. Check `package.json` and `vite.config.ts` first, and run `vp run <name>` when the project defines a script or task with that name.
|
|
12
12
|
|
|
13
|
+
## Tool Versions
|
|
14
|
+
|
|
15
|
+
Run `vp toolchain` to show versions and relationships in the active Vite+
|
|
16
|
+
release. Add a tool name to select part of the graph. For example, run
|
|
17
|
+
`vp toolchain vite`. Use `--global` to ignore the local `vite-plus` package. Use
|
|
18
|
+
`vp why <package>` to show the package-manager dependency graph.
|
|
19
|
+
|
|
13
20
|
## Review Checklist
|
|
14
21
|
|
|
15
22
|
- [ ] Run `vp install` after pulling remote changes and before getting started.
|
|
@@ -1,37 +1,51 @@
|
|
|
1
1
|
# Hono fullstack monorepo
|
|
2
2
|
|
|
3
|
-
TanStack Start and Nitro serve the frontend while mounting the independently buildable `@repo/server` Hono application in-process under `/api`. SQLite/Drizzle and shared definitions live in their own packages.
|
|
3
|
+
TanStack Start and Nitro serve the frontend while mounting the independently buildable `@repo/server` Hono application in-process under `/api`. The same stack can run on Cloudflare Workers with D1. SQLite/Drizzle and shared definitions live in their own packages.
|
|
4
4
|
|
|
5
5
|
## Layout
|
|
6
6
|
|
|
7
|
-
- `apps/stack/frontend`: TanStack Start UI, Router, React Query, Jotai,
|
|
8
|
-
- `apps/stack/
|
|
7
|
+
- `apps/stack/frontend`: TanStack Start UI, Router, React Query, Jotai, shared API client, and the `/api/$` server route.
|
|
8
|
+
- `apps/stack/backend`: runtime-specific adapters for `@repo/server/nodejs` and `@repo/server/cloudflare`.
|
|
9
9
|
- `apps/server`: independently buildable and startable Hono server and worker runtime.
|
|
10
10
|
- `libs/definition`: backend/frontend contracts and environment definitions.
|
|
11
|
-
- `libs/db`: SQLite/Drizzle
|
|
11
|
+
- `libs/db`: SQLite/Drizzle and D1 clients, schema, relations, and manual migration commands.
|
|
12
12
|
|
|
13
13
|
## Setup
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
pnpm install
|
|
17
|
+
pnpm --filter @repo/server cf:typegen
|
|
18
|
+
pnpm --filter @repo/stack cf:typegen
|
|
19
|
+
cp apps/stack/.dev.vars.example apps/stack/.dev.vars
|
|
17
20
|
cp envs/.env.example.server envs/.env.server
|
|
18
21
|
cp envs/.env.example.sqlite envs/.env.sqlite
|
|
22
|
+
cp envs/.env.example.sqlite-d1 envs/.env.sqlite-d1
|
|
19
23
|
pnpm prepare
|
|
20
|
-
pnpm dev:sqlite:push
|
|
21
|
-
pnpm dev
|
|
24
|
+
pnpm --filter @repo/db dev:sqlite:push
|
|
25
|
+
pnpm -C apps/stack dev:node-chain
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
Database schema changes remain manual
|
|
28
|
+
Database schema changes remain manual. Cloudflare D1 uses the same SQLite schema and migration directory with separate credentials and commands. Production Worker variables are configured in the Cloudflare dashboard; `.dev.vars` is only for local development.
|
|
29
|
+
|
|
30
|
+
Cloudflare runtime and binding types are generated from each runnable Worker's Wrangler config. Rerun the corresponding `cf:typegen` command after changing compatibility settings or bindings.
|
|
31
|
+
|
|
32
|
+
Before deploying, replace the placeholder D1 `database_id` and `database_name` in both Wrangler configs. Keep production variables in the Cloudflare dashboard rather than committing them.
|
|
33
|
+
|
|
34
|
+
Example tasks run in-process through `PassThroughWorkRunner` by default. The complete Piscina implementation remains available: enable `PiscinaWorkRunner` in the Node.js adapter and the adjacent worker pack entry in `apps/server/vite.config.ts` to use worker threads.
|
|
25
35
|
|
|
26
36
|
## Commands
|
|
27
37
|
|
|
28
|
-
- `pnpm dev`:
|
|
38
|
+
- `pnpm -C apps/stack dev:node-chain`: build upstream dependencies, then watch the Node stack and its dependencies.
|
|
39
|
+
- `pnpm -C apps/stack dev:cloudflare-chain`: build upstream dependencies, then watch the Cloudflare stack and its dependencies.
|
|
29
40
|
- `pnpm build`: build all workspace packages, including Nitro's `.output`.
|
|
30
|
-
- `pnpm dev:sqlite:push`: push the SQLite schema manually
|
|
31
|
-
- `pnpm dev:sqlite:gen` / `pnpm dev:sqlite:migrate`: generate or apply migrations manually.
|
|
32
|
-
- `pnpm --filter @repo/
|
|
33
|
-
- `pnpm --filter @repo/server
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
- `pnpm --filter @repo/db dev:sqlite:push`: push the local SQLite schema manually.
|
|
42
|
+
- `pnpm --filter @repo/db dev:sqlite:gen` / `pnpm --filter @repo/db dev:sqlite:migrate`: generate or apply SQLite migrations manually.
|
|
43
|
+
- `pnpm --filter @repo/db dev:d1:gen` / `pnpm --filter @repo/db dev:d1:migrate`: generate or apply D1 migrations manually.
|
|
44
|
+
- `pnpm --filter @repo/server cf:typegen` / `pnpm --filter @repo/stack cf:typegen`: generate Cloudflare runtime and binding types.
|
|
45
|
+
- `pnpm --filter @repo/server dev`: start Hono independently on Node.js.
|
|
46
|
+
- `pnpm --filter @repo/server dev:cloudflare`: start Hono independently on local workerd and D1.
|
|
47
|
+
- `pnpm --filter @repo/server test`: run the retained server tests.
|
|
48
|
+
|
|
49
|
+
The stack exposes health at `/api/health`, OpenAPI YAML at `/api/docs/openapi.yaml`, Swagger at `/api/swagger`, CRUD at `/api/examples`, and worker processing at `/api/examples/process`. The independently started Hono app uses `SERVER_BASE_PATH`.
|
|
36
50
|
|
|
37
51
|
The example vertical slice may be removed after replacing its definition, table, Hono routes, worker task, API hooks, and page together. Docker and CI are intentionally not included in this Fullstack template.
|
|
@@ -2,19 +2,25 @@
|
|
|
2
2
|
"name": "@repo/server",
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"files": [
|
|
6
|
-
"dist"
|
|
7
|
-
],
|
|
5
|
+
"files": ["dist"],
|
|
8
6
|
"type": "module",
|
|
9
7
|
"exports": {
|
|
10
|
-
"./
|
|
8
|
+
"./nodejs": "./dist/nodejsEntry.mjs",
|
|
9
|
+
"./cloudflare": "./dist/cloudflareEntry.mjs",
|
|
11
10
|
"./package.json": "./package.json"
|
|
12
11
|
},
|
|
13
12
|
"scripts": {
|
|
14
|
-
"dev": "
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
13
|
+
"dev": "pnpm dev:node",
|
|
14
|
+
"build": "pnpm build:node",
|
|
15
|
+
"dev:node-chain": "turbo watch dev:node --filter=@repo/server",
|
|
16
|
+
"dev:node": "concurrently -k -n dev,pack -c blue,green \"vp dev\" \"VITE_PUBLIC_DEV_SERVE=1 vp pack --watch\"",
|
|
17
|
+
"dev:cloudflare-chain": "turbo watch dev:cloudflare --filter=@repo/server",
|
|
18
|
+
"dev:cloudflare": "wrangler dev --config wrangler.jsonc --persist-to ../../.wrangler/state",
|
|
19
|
+
"db:migrate:cloudflare:local": "wrangler d1 migrations apply DB --local --config wrangler.jsonc --persist-to ../../.wrangler/state",
|
|
20
|
+
"build:node-chain": "turbo build:node --filter=@repo/server",
|
|
21
|
+
"build:node": "vp pack",
|
|
22
|
+
"cf:typegen": "wrangler types --config wrangler.jsonc",
|
|
23
|
+
"cf:typecheck": "wrangler types --config wrangler.jsonc --check",
|
|
18
24
|
"test": "vp test",
|
|
19
25
|
"check": "vp check"
|
|
20
26
|
},
|
|
@@ -28,7 +34,7 @@
|
|
|
28
34
|
"change-case": "catalog:shared",
|
|
29
35
|
"defu": "catalog:shared",
|
|
30
36
|
"dotenv": "catalog:",
|
|
31
|
-
"drizzle-orm": "catalog:
|
|
37
|
+
"drizzle-orm": "catalog:db",
|
|
32
38
|
"hono": "catalog:server",
|
|
33
39
|
"hono-openapi": "catalog:server",
|
|
34
40
|
"hono-pino": "catalog:server",
|
|
@@ -51,6 +57,7 @@
|
|
|
51
57
|
"pino-pretty": "catalog:server",
|
|
52
58
|
"tsx": "catalog:",
|
|
53
59
|
"typescript": "catalog:",
|
|
54
|
-
"vite-plus": "catalog:"
|
|
60
|
+
"vite-plus": "catalog:",
|
|
61
|
+
"wrangler": "catalog:"
|
|
55
62
|
}
|
|
56
63
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createD1Client } from '@repo/db/sqlite/d1-client'
|
|
2
|
+
import { env } from 'cloudflare:workers'
|
|
3
|
+
import { getRuntimeKey } from 'hono/adapter'
|
|
4
|
+
import { createApp } from '@/app'
|
|
5
|
+
import { resolveConfig } from '@/config'
|
|
6
|
+
import { setRootLogger } from '@/infra/logger/getter'
|
|
7
|
+
import { createPinoLogger } from '@/infra/logger/pino'
|
|
8
|
+
import { PassThroughWorkRunner } from '@/infra/work-runner/pass-through/runner'
|
|
9
|
+
import type { AppDependencies, AppLogger } from '@/shared/app/types'
|
|
10
|
+
|
|
11
|
+
const appConfig = resolveConfig(env as any)
|
|
12
|
+
const { runtime, log } = appConfig
|
|
13
|
+
const { rootLogger, loggerHandler } = createPinoLogger({
|
|
14
|
+
isLocal: runtime.isLocal,
|
|
15
|
+
logBase: log.base,
|
|
16
|
+
runtime: getRuntimeKey(),
|
|
17
|
+
logRoll: log.roll,
|
|
18
|
+
logPretty: log.pretty,
|
|
19
|
+
})
|
|
20
|
+
setRootLogger(rootLogger as unknown as AppLogger)
|
|
21
|
+
|
|
22
|
+
export const workRunner = new PassThroughWorkRunner()
|
|
23
|
+
const d1Client = createD1Client(env.DB)
|
|
24
|
+
const dependencies: AppDependencies = {
|
|
25
|
+
appConfig,
|
|
26
|
+
loggerHandler,
|
|
27
|
+
workRunner,
|
|
28
|
+
db: d1Client.db,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const app = createApp({
|
|
32
|
+
serverBasePath: appConfig.runtime.serverBasePath,
|
|
33
|
+
makeDependencies: () => dependencies,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export default app
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { serve } from '@hono/node-server'
|
|
2
|
-
import app, {
|
|
2
|
+
import app, { closeNodeDependencies } from './entry'
|
|
3
3
|
|
|
4
4
|
function main() {
|
|
5
5
|
const port = Number(process.env.NODE_PORT)
|
|
@@ -38,11 +38,8 @@ function main() {
|
|
|
38
38
|
})
|
|
39
39
|
console.log('✔ HTTP server successfully closed.')
|
|
40
40
|
|
|
41
|
-
await
|
|
42
|
-
console.log('✔
|
|
43
|
-
|
|
44
|
-
sqliteClient.close()
|
|
45
|
-
console.log('✔ SQLite client successfully closed.')
|
|
41
|
+
await closeNodeDependencies()
|
|
42
|
+
console.log('✔ Node dependencies successfully closed.')
|
|
46
43
|
|
|
47
44
|
console.log('Graceful shutdown completed.')
|
|
48
45
|
process.exit(0)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createSqliteClient } from '@repo/db/sqlite/sqlite'
|
|
2
2
|
import { sqliteSchemaConfig } from '@repo/definition/backend'
|
|
3
3
|
import { createApp } from '@/app'
|
|
4
|
-
import { resolveConfig } from '
|
|
4
|
+
import { resolveConfig } from '@/config'
|
|
5
5
|
import { createPinoLogger } from '@/infra/logger/pino'
|
|
6
6
|
import { getRuntimeKey } from 'hono/adapter'
|
|
7
7
|
import type { AppDependencies, AppLogger } from '@/shared/app/types'
|
|
8
|
-
import {
|
|
8
|
+
// import { PiscinaWorkRunner } from '@/infra/work-runner/piscina/runner'
|
|
9
|
+
import { PassThroughWorkRunner } from '@/infra/work-runner/pass-through/runner'
|
|
9
10
|
import { getLogger, setRootLogger } from '@/infra/logger/getter'
|
|
10
11
|
|
|
11
12
|
function installProcessErrorHandlers(isLocal: boolean) {
|
|
@@ -35,13 +36,27 @@ const { rootLogger, loggerHandler } = createPinoLogger({
|
|
|
35
36
|
setRootLogger(rootLogger as unknown as AppLogger)
|
|
36
37
|
installProcessErrorHandlers(runtime.isLocal)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
// To enable Piscina, use PiscinaWorkRunner and enable the worker pack entry in vite.config.ts.
|
|
40
|
+
// export const workRunner = PiscinaWorkRunner.getInstance(appConfig.worker)
|
|
41
|
+
export const workRunner = new PassThroughWorkRunner()
|
|
39
42
|
export const sqliteClient = createSqliteClient(sqliteConfig.dbPath)
|
|
40
43
|
|
|
41
44
|
let dependencies: AppDependencies | undefined
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
let closePromise: Promise<void> | undefined
|
|
46
|
+
const app = createApp({
|
|
47
|
+
serverBasePath: appConfig.runtime.serverBasePath,
|
|
48
|
+
makeDependencies: () => {
|
|
49
|
+
dependencies ||= { appConfig, loggerHandler, workRunner, db: sqliteClient.db }
|
|
50
|
+
return dependencies
|
|
51
|
+
},
|
|
45
52
|
})
|
|
46
53
|
|
|
54
|
+
export const closeNodeDependencies = (): Promise<void> => {
|
|
55
|
+
closePromise ||= (async () => {
|
|
56
|
+
await workRunner.shutdown()
|
|
57
|
+
sqliteClient.close()
|
|
58
|
+
})()
|
|
59
|
+
return closePromise
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
export default app
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
2
|
import { contextStorage } from 'hono/context-storage'
|
|
3
3
|
import { csrf } from 'hono/csrf'
|
|
4
4
|
import { secureHeaders } from 'hono/secure-headers'
|
|
5
5
|
import { requestId } from 'hono/request-id'
|
|
6
6
|
import { corsMiddleware } from './middlewares/cors'
|
|
7
7
|
import { exampleRoutes } from './modules/example/routes'
|
|
8
|
-
import type {
|
|
8
|
+
import type { CreateAppOptions, AppEnv } from './shared/app/types'
|
|
9
9
|
import { E } from './shared/errors/defs'
|
|
10
10
|
import { globalErrorHandler } from './shared/errors/handler'
|
|
11
11
|
import { configureOpenAPI } from './shared/network/openapi'
|
|
12
12
|
import { objectEntries } from './shared/utils'
|
|
13
13
|
|
|
14
|
-
export const createApp = (
|
|
15
|
-
|
|
16
|
-
): Hono<AppEnv> => {
|
|
17
|
-
const app = new Hono<AppEnv>()
|
|
14
|
+
export const createApp = ({ serverBasePath, makeDependencies }: CreateAppOptions): Hono<AppEnv> => {
|
|
15
|
+
const app = new Hono<AppEnv>().basePath(serverBasePath)
|
|
18
16
|
|
|
19
17
|
app.use('*', async (c, next) => {
|
|
20
18
|
const deps = await makeDependencies(c)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
corsConfig,
|
|
3
|
+
logBaseConfig,
|
|
4
|
+
logPrettyRawConfig,
|
|
5
|
+
logRollRawConfig,
|
|
6
|
+
logRollSchemaConfig,
|
|
7
|
+
runtimeConfig,
|
|
8
|
+
workerConfig,
|
|
9
|
+
} from '@repo/definition/backend'
|
|
10
|
+
import { getRuntimeKey } from 'hono/adapter'
|
|
11
|
+
import { flatten as flattenValiIssues, isValiError } from 'valibot'
|
|
12
|
+
import type { AppConfig } from '@/shared/app/types'
|
|
13
|
+
|
|
14
|
+
export function resolveConfig(context: Record<string, unknown>): AppConfig {
|
|
15
|
+
try {
|
|
16
|
+
const fromEnv = { context, matchContextKey: 'constantCase' as const }
|
|
17
|
+
const runtime = runtimeConfig.resolve(fromEnv)
|
|
18
|
+
const runtimeKey = getRuntimeKey()
|
|
19
|
+
const isNodejsLike = runtimeKey === 'node' || runtimeKey === 'bun'
|
|
20
|
+
const log: AppConfig['log'] = { base: logBaseConfig.resolve(fromEnv) }
|
|
21
|
+
|
|
22
|
+
if (isNodejsLike) {
|
|
23
|
+
log.roll = {
|
|
24
|
+
...logRollSchemaConfig.resolve(fromEnv),
|
|
25
|
+
...logRollRawConfig.resolve(),
|
|
26
|
+
}
|
|
27
|
+
log.pretty = logPrettyRawConfig.resolve()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
runtime: { ...runtime, isLocal: runtime.serverEnv === 'local' },
|
|
32
|
+
log,
|
|
33
|
+
worker: workerConfig.resolve(fromEnv),
|
|
34
|
+
middleware: {
|
|
35
|
+
cors: corsConfig.resolve({
|
|
36
|
+
context: { domains: context.CORS_DOMAINS },
|
|
37
|
+
matchContextKey: 'camel',
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (isValiError(error)) {
|
|
43
|
+
throw new Error(`[resolveConfig] ${JSON.stringify(flattenValiIssues(error.issues))}`)
|
|
44
|
+
}
|
|
45
|
+
throw error
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -37,6 +37,10 @@ export const createPinoOptions = ({
|
|
|
37
37
|
}) => {
|
|
38
38
|
const pinoOptions: LoggerOptions = {
|
|
39
39
|
level: baseConfig.loggerLevel || (isLocal ? 'debug' : 'info'),
|
|
40
|
+
browser: {
|
|
41
|
+
asObject: true,
|
|
42
|
+
serialize: ['!stdSerializers.err', 'req', 'res', 'err'],
|
|
43
|
+
},
|
|
40
44
|
serializers: {
|
|
41
45
|
req: (req) => {
|
|
42
46
|
return {
|
|
@@ -1,34 +1,13 @@
|
|
|
1
1
|
/** WORKER 打包入口 */
|
|
2
|
-
import type { RunnerTaskInvocation
|
|
2
|
+
import type { RunnerTaskInvocation } from '../types'
|
|
3
3
|
import {
|
|
4
4
|
executeExampleTask as runExampleTask,
|
|
5
5
|
type ExampleRunnerTaskModel,
|
|
6
6
|
} from '@/modules/example/work-task/tasks'
|
|
7
|
-
import { isWorkerTaskBusinessError } from '../utils/business-error'
|
|
8
7
|
|
|
9
|
-
export
|
|
8
|
+
export function executeExampleTask({
|
|
10
9
|
input,
|
|
11
10
|
messagePort,
|
|
12
|
-
}: RunnerTaskInvocation<ExampleRunnerTaskModel['input']>):
|
|
13
|
-
|
|
14
|
-
> {
|
|
15
|
-
try {
|
|
16
|
-
const result = await runExampleTask(input, messagePort)
|
|
17
|
-
messagePort.postMessage({ type: 'result', data: result.data })
|
|
18
|
-
return result
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if (!isWorkerTaskBusinessError(error)) throw error
|
|
21
|
-
const result = {
|
|
22
|
-
ok: false,
|
|
23
|
-
error: {
|
|
24
|
-
code: 'EXAMPLE_PROCESS_FAILED' as const,
|
|
25
|
-
message: error.message,
|
|
26
|
-
...(error.details ? { details: error.details } : {}),
|
|
27
|
-
},
|
|
28
|
-
}
|
|
29
|
-
messagePort.postMessage({ type: 'error', error: result.error })
|
|
30
|
-
return result
|
|
31
|
-
} finally {
|
|
32
|
-
messagePort.close()
|
|
33
|
-
}
|
|
11
|
+
}: RunnerTaskInvocation<ExampleRunnerTaskModel['input']>): ReturnType<typeof runExampleTask> {
|
|
12
|
+
return runExampleTask(input, messagePort).finally(() => messagePort.close())
|
|
34
13
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { executeExampleTask } from '../example/worker-entry'
|
|
2
|
+
import { getLogger } from '@/infra/logger/getter'
|
|
3
|
+
import { WorkSubject } from '../constants'
|
|
4
|
+
import type {
|
|
5
|
+
AppWorkRunner,
|
|
6
|
+
RunnerTaskParams,
|
|
7
|
+
RunnerTaskRunOptions,
|
|
8
|
+
RunnerTaskRunResult,
|
|
9
|
+
WorkerMessage,
|
|
10
|
+
WorkerMessagePort,
|
|
11
|
+
} from '../types'
|
|
12
|
+
import { handleWorkerMessage } from '../utils/message'
|
|
13
|
+
|
|
14
|
+
function createMessagePort(subject: WorkSubject): WorkerMessagePort {
|
|
15
|
+
const logger = getLogger()
|
|
16
|
+
return {
|
|
17
|
+
postMessage(message: WorkerMessage) {
|
|
18
|
+
handleWorkerMessage(message, logger, { subject })
|
|
19
|
+
},
|
|
20
|
+
close() {},
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class PassThroughWorkRunner implements AppWorkRunner {
|
|
25
|
+
async run<TSubject extends WorkSubject>(
|
|
26
|
+
taskParams: RunnerTaskParams<TSubject>,
|
|
27
|
+
options?: RunnerTaskRunOptions,
|
|
28
|
+
): Promise<RunnerTaskRunResult<TSubject>> {
|
|
29
|
+
if (taskParams.subject !== WorkSubject.PROCESS_EXAMPLE) {
|
|
30
|
+
throw new Error(`[PassThroughWorkRunner] task name not found: ${String(taskParams.subject)}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
options?.signal?.throwIfAborted()
|
|
34
|
+
|
|
35
|
+
return executeExampleTask({
|
|
36
|
+
input: taskParams.input,
|
|
37
|
+
messagePort: createMessagePort(taskParams.subject),
|
|
38
|
+
}) as Promise<RunnerTaskRunResult<TSubject>>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async shutdown(): Promise<void> {}
|
|
42
|
+
}
|
|
@@ -14,21 +14,21 @@ import path from 'node:path'
|
|
|
14
14
|
import { MessageChannel, type MessagePort } from 'node:worker_threads'
|
|
15
15
|
import workersDir from 'virtual:workers'
|
|
16
16
|
import { getLogger } from '@/infra/logger/getter'
|
|
17
|
-
import type * as ExampleWorker from '
|
|
17
|
+
import type * as ExampleWorker from '../example/worker-entry'
|
|
18
18
|
import { handleWorkerMessage } from '../utils/message'
|
|
19
19
|
|
|
20
20
|
const taskNameMap = {
|
|
21
21
|
[WorkSubject.PROCESS_EXAMPLE]: 'executeExampleTask',
|
|
22
22
|
} satisfies Partial<Record<WorkSubject, keyof typeof ExampleWorker>>
|
|
23
23
|
|
|
24
|
-
export class
|
|
25
|
-
static #instance:
|
|
24
|
+
export class PiscinaWorkRunner implements AppWorkRunner {
|
|
25
|
+
static #instance: PiscinaWorkRunner | null = null
|
|
26
26
|
readonly #pool: Piscina
|
|
27
27
|
readonly #config: WorkerConfig
|
|
28
28
|
|
|
29
29
|
private constructor(config: WorkerConfig) {
|
|
30
30
|
if (!config) {
|
|
31
|
-
throw new Error('[
|
|
31
|
+
throw new Error('[PiscinaWorkRunner] config is required')
|
|
32
32
|
}
|
|
33
33
|
this.#pool = new Piscina({
|
|
34
34
|
filename: path.join(workersDir, 'example-worker.mjs'),
|
|
@@ -40,10 +40,10 @@ export class ExampleWorkRunner implements AppWorkRunner {
|
|
|
40
40
|
this.#config = config
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
public static getInstance(config: WorkerConfig):
|
|
43
|
+
public static getInstance(config: WorkerConfig): PiscinaWorkRunner {
|
|
44
44
|
if (!this.#instance) {
|
|
45
|
-
getLogger().info({ worker: config }, '
|
|
46
|
-
this.#instance = new
|
|
45
|
+
getLogger().info({ worker: config }, 'PiscinaWorkRunner initialized')
|
|
46
|
+
this.#instance = new PiscinaWorkRunner(config)
|
|
47
47
|
}
|
|
48
48
|
return this.#instance
|
|
49
49
|
}
|
|
@@ -54,7 +54,7 @@ export class ExampleWorkRunner implements AppWorkRunner {
|
|
|
54
54
|
): Promise<RunnerTaskRunResult<TSubject>> {
|
|
55
55
|
const targetName = taskNameMap[taskParams.subject]
|
|
56
56
|
if (!targetName) {
|
|
57
|
-
throw new Error(`[
|
|
57
|
+
throw new Error(`[PiscinaWorkRunner] task name not found: ${taskParams.subject}`)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
let timeoutTimer: ReturnType<typeof setTimeout> | undefined
|
|
@@ -180,6 +180,6 @@ export class ExampleWorkRunner implements AppWorkRunner {
|
|
|
180
180
|
|
|
181
181
|
async shutdown(): Promise<void> {
|
|
182
182
|
await this.#pool.close()
|
|
183
|
-
if (
|
|
183
|
+
if (PiscinaWorkRunner.#instance === this) PiscinaWorkRunner.#instance = null
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExampleRunnerTaskModel } from '@/modules/example/work-task/tasks'
|
|
2
|
-
import type {
|
|
2
|
+
import type { Transferable } from 'node:worker_threads'
|
|
3
3
|
import type { WorkerErrorCode, WorkSubject } from './constants'
|
|
4
4
|
|
|
5
5
|
// export const WorkerInvocationType = Object.freeze({
|
|
@@ -43,7 +43,7 @@ export type RunnerTaskErrorCode<TSubject extends WorkSubject> =
|
|
|
43
43
|
|
|
44
44
|
export type RunnerTaskInvocation<TInput> = {
|
|
45
45
|
input: TInput
|
|
46
|
-
messagePort:
|
|
46
|
+
messagePort: WorkerMessagePort
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export type RunnerTaskJsonValue =
|
|
@@ -78,6 +78,11 @@ export type WorkerMessage =
|
|
|
78
78
|
error: RunnerTaskError
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
export interface WorkerMessagePort {
|
|
82
|
+
postMessage(message: WorkerMessage): void
|
|
83
|
+
close(): void
|
|
84
|
+
}
|
|
85
|
+
|
|
81
86
|
export type RunnerTaskError<TCode extends string = string> = {
|
|
82
87
|
code: TCode
|
|
83
88
|
/** 仅供服务端诊断,不应直接作为 HTTP 响应文案。 */
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { AppLogger } from '@/shared/app/types'
|
|
2
|
-
import type { RunnerTaskErrorDetails, WorkerMessage } from '../types'
|
|
3
|
-
import type { MessagePort } from 'node:worker_threads'
|
|
2
|
+
import type { RunnerTaskErrorDetails, WorkerMessage, WorkerMessagePort } from '../types'
|
|
4
3
|
import type { WorkSubject } from '../constants'
|
|
5
4
|
|
|
6
5
|
export function workerLog(
|
|
7
|
-
messagePort:
|
|
6
|
+
messagePort: WorkerMessagePort,
|
|
8
7
|
level: Extract<WorkerMessage, { type: 'log' }>['level'],
|
|
9
8
|
message: string,
|
|
10
9
|
data?: RunnerTaskErrorDetails,
|
|
@@ -12,7 +11,11 @@ export function workerLog(
|
|
|
12
11
|
messagePort.postMessage({ type: 'log', level, message, data } satisfies WorkerMessage)
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
export function workerProgress(
|
|
14
|
+
export function workerProgress(
|
|
15
|
+
messagePort: WorkerMessagePort,
|
|
16
|
+
value: number,
|
|
17
|
+
message?: string,
|
|
18
|
+
): void {
|
|
16
19
|
messagePort.postMessage({ type: 'progress', value, message } satisfies WorkerMessage)
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -43,10 +43,8 @@ export const exampleRoutes = new Hono<AppEnv>()
|
|
|
43
43
|
}),
|
|
44
44
|
generalValidator('json', createExampleInputSchema),
|
|
45
45
|
async (c) => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
.values(c.req.valid('json'))
|
|
49
|
-
.returning(vcExamples.default)
|
|
46
|
+
const input = c.req.valid('json')
|
|
47
|
+
const [row] = await getDb(c).insert(examples).values(input).returning(vcExamples.default)
|
|
50
48
|
return successJson(c, row)
|
|
51
49
|
},
|
|
52
50
|
)
|
|
@@ -60,7 +58,7 @@ export const exampleRoutes = new Hono<AppEnv>()
|
|
|
60
58
|
}),
|
|
61
59
|
generalValidator('json', processExampleInputSchema),
|
|
62
60
|
async (c) => {
|
|
63
|
-
const result = await c.get('
|
|
61
|
+
const result = await c.get('workRunner').run({
|
|
64
62
|
subject: WorkSubject.PROCESS_EXAMPLE,
|
|
65
63
|
input: c.req.valid('json'),
|
|
66
64
|
})
|