@oyolincc/create 0.0.0-dev-20260922064135 → 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.
Files changed (126) hide show
  1. package/package.json +1 -1
  2. package/templates/hono-fullstack/.vscode/launch.json +14 -3
  3. package/templates/hono-fullstack/.vscode/settings.json +3 -1
  4. package/templates/hono-fullstack/AGENTS.md +7 -0
  5. package/templates/hono-fullstack/README.md +28 -14
  6. package/templates/hono-fullstack/_gitignore +4 -0
  7. package/templates/hono-fullstack/apps/server/.dev.vars.example +9 -0
  8. package/templates/hono-fullstack/apps/server/package.json +17 -9
  9. package/templates/hono-fullstack/apps/server/src/adapters/cloudflare/entry.ts +36 -0
  10. package/templates/hono-fullstack/apps/server/src/adapters/nodejs/bootstrap.ts +3 -6
  11. package/templates/hono-fullstack/apps/server/src/adapters/nodejs/entry.ts +17 -5
  12. package/templates/hono-fullstack/apps/server/src/app.ts +2 -2
  13. package/templates/hono-fullstack/apps/server/src/config.ts +47 -0
  14. package/templates/hono-fullstack/apps/server/src/infra/logger/pino.ts +4 -0
  15. package/templates/hono-fullstack/apps/server/src/infra/work-runner/example/worker-entry.ts +4 -25
  16. package/templates/hono-fullstack/apps/server/src/infra/work-runner/pass-through/runner.ts +42 -0
  17. package/templates/{hono-server/apps/server/src/infra/work-runner/example → hono-fullstack/apps/server/src/infra/work-runner/piscina}/runner.ts +9 -9
  18. package/templates/hono-fullstack/apps/server/src/infra/work-runner/types.ts +7 -2
  19. package/templates/hono-fullstack/apps/server/src/infra/work-runner/utils/message.ts +7 -4
  20. package/templates/hono-fullstack/apps/server/src/modules/example/routes.ts +3 -5
  21. package/templates/hono-fullstack/apps/server/src/modules/example/work-task/tasks.test.ts +17 -5
  22. package/templates/hono-fullstack/apps/server/src/modules/example/work-task/tasks.ts +38 -15
  23. package/templates/hono-fullstack/apps/server/src/shared/app/types.ts +3 -4
  24. package/templates/hono-fullstack/apps/server/tsconfig.json +1 -0
  25. package/templates/hono-fullstack/apps/server/vite.config.ts +8 -3
  26. package/templates/hono-fullstack/apps/server/wrangler.jsonc +29 -0
  27. package/templates/hono-fullstack/apps/stack/backend/cloudflare.ts +5 -0
  28. package/templates/hono-fullstack/apps/stack/backend/node.ts +3 -0
  29. package/templates/hono-fullstack/apps/stack/frontend/components/general/ErrorMessage.tsx +4 -0
  30. package/templates/hono-fullstack/apps/stack/frontend/components/layouts/AppLayout.tsx +11 -0
  31. package/templates/hono-fullstack/apps/stack/frontend/components/layouts/Header.tsx +7 -0
  32. package/templates/hono-fullstack/apps/stack/frontend/components/layouts/Page.tsx +5 -0
  33. package/templates/hono-fullstack/apps/stack/frontend/components/layouts/PageContainer.tsx +5 -0
  34. package/templates/hono-fullstack/apps/stack/frontend/modules/example/components/ExampleForm.tsx +45 -0
  35. package/templates/hono-fullstack/apps/stack/frontend/modules/example/components/ExampleList.tsx +13 -0
  36. package/templates/hono-fullstack/apps/stack/frontend/routeTree.gen.ts +21 -4
  37. package/templates/hono-fullstack/apps/stack/frontend/routes/api/$.ts +18 -0
  38. package/templates/hono-fullstack/apps/stack/frontend/routes/index.tsx +48 -2
  39. package/templates/hono-fullstack/apps/stack/frontend/vite-env.d.ts +5 -0
  40. package/templates/hono-fullstack/apps/stack/package.json +20 -5
  41. package/templates/hono-fullstack/apps/stack/tsconfig.json +2 -1
  42. package/templates/hono-fullstack/apps/stack/vite.config.ts +75 -26
  43. package/templates/hono-fullstack/apps/stack/wrangler.jsonc +20 -0
  44. package/templates/hono-fullstack/envs/.env.example.server +1 -1
  45. package/templates/hono-fullstack/envs/.env.example.sqlite-d1 +3 -0
  46. package/templates/hono-fullstack/libs/db/drizzle-kit/sqlite-d1.config.ts +22 -0
  47. package/templates/hono-fullstack/libs/db/package.json +8 -6
  48. package/templates/hono-fullstack/libs/db/src/dialects/sqlite/d1-client.ts +12 -0
  49. package/templates/hono-fullstack/libs/db/src/dialects/sqlite/schema/columns.ts +5 -0
  50. package/templates/hono-fullstack/libs/db/src/dialects/sqlite/schema/modules/examples/examples.ts +2 -2
  51. package/templates/hono-fullstack/libs/db/vite.config.ts +1 -0
  52. package/templates/hono-fullstack/libs/definition/src/configs/defs/runtime.ts +1 -0
  53. package/templates/hono-fullstack/libs/definition/src/configs/defs/sqlite.ts +16 -3
  54. package/templates/hono-fullstack/oyo.config.json +2 -1
  55. package/templates/hono-fullstack/package.json +1 -1
  56. package/templates/hono-fullstack/pnpm-workspace.yaml +7 -5
  57. package/templates/hono-fullstack/turbo.json +25 -3
  58. package/templates/hono-fullstack/vite.config.ts +1 -0
  59. package/templates/hono-server/.vscode/launch.json +7 -3
  60. package/templates/hono-server/.vscode/settings.json +3 -1
  61. package/templates/hono-server/AGENTS.md +7 -0
  62. package/templates/hono-server/README.md +25 -12
  63. package/templates/hono-server/_gitignore +4 -0
  64. package/templates/hono-server/apps/server/.dev.vars.example +9 -0
  65. package/templates/hono-server/apps/server/package.json +18 -8
  66. package/templates/hono-server/apps/server/src/adapters/cloudflare/entry.ts +36 -0
  67. package/templates/hono-server/apps/server/src/adapters/nodejs/bootstrap.ts +3 -3
  68. package/templates/hono-server/apps/server/src/adapters/nodejs/entry.ts +24 -4
  69. package/templates/hono-server/apps/server/src/app.ts +2 -2
  70. package/templates/hono-server/apps/server/src/config.ts +47 -0
  71. package/templates/hono-server/apps/server/src/infra/logger/pino.ts +4 -0
  72. package/templates/hono-server/apps/server/src/infra/work-runner/example/worker-entry.ts +4 -25
  73. package/templates/hono-server/apps/server/src/infra/work-runner/pass-through/runner.ts +42 -0
  74. package/templates/{hono-fullstack/apps/server/src/infra/work-runner/example → hono-server/apps/server/src/infra/work-runner/piscina}/runner.ts +9 -9
  75. package/templates/hono-server/apps/server/src/infra/work-runner/types.ts +7 -2
  76. package/templates/hono-server/apps/server/src/infra/work-runner/utils/message.ts +7 -4
  77. package/templates/hono-server/apps/server/src/modules/example/contracts.ts +4 -0
  78. package/templates/hono-server/apps/server/src/modules/example/routes.ts +32 -5
  79. package/templates/hono-server/apps/server/src/modules/example/work-task/tasks.test.ts +17 -5
  80. package/templates/hono-server/apps/server/src/modules/example/work-task/tasks.ts +38 -15
  81. package/templates/hono-server/apps/server/src/shared/app/types.ts +3 -4
  82. package/templates/hono-server/apps/server/tsconfig.json +1 -0
  83. package/templates/hono-server/apps/server/vite.config.ts +8 -3
  84. package/templates/hono-server/apps/server/wrangler.jsonc +29 -0
  85. package/templates/hono-server/docker/server.Dockerfile +3 -5
  86. package/templates/hono-server/envs/.env.example.server +1 -1
  87. package/templates/hono-server/envs/.env.example.sqlite +1 -0
  88. package/templates/hono-server/envs/.env.example.sqlite-d1 +3 -0
  89. package/templates/hono-server/libs/db/drizzle-kit/sqlite-d1.config.ts +22 -0
  90. package/templates/hono-server/libs/db/drizzle-kit/sqlite.config.ts +22 -0
  91. package/templates/hono-server/libs/db/package.json +42 -0
  92. package/templates/hono-server/libs/db/src/dialects/sqlite/client.ts +35 -0
  93. package/templates/hono-server/libs/db/src/dialects/sqlite/d1-client.ts +12 -0
  94. package/templates/hono-server/libs/db/src/dialects/sqlite/schema/columns.ts +37 -0
  95. package/templates/hono-server/libs/db/src/dialects/sqlite/schema/index.ts +7 -0
  96. package/templates/hono-server/libs/db/src/dialects/sqlite/schema/modules/examples/examples.ts +22 -0
  97. package/templates/hono-server/libs/db/src/dialects/sqlite/schema/relations.ts +5 -0
  98. package/templates/hono-server/libs/db/src/dialects/sqlite/schema/tables.ts +5 -0
  99. package/templates/hono-server/libs/db/src/dialects/sqlite/schema/types.ts +7 -0
  100. package/templates/hono-server/libs/db/src/dialects/sqlite/schema/vo.ts +1 -0
  101. package/templates/hono-server/libs/db/src/dialects/sqlite/utils.ts +8 -0
  102. package/templates/hono-server/libs/db/src/shared/types.ts +26 -0
  103. package/templates/hono-server/libs/db/tsconfig.json +10 -0
  104. package/templates/hono-server/libs/db/vite.config.ts +22 -0
  105. package/templates/hono-server/libs/definition/src/configs/defs/runtime.ts +1 -0
  106. package/templates/hono-server/libs/definition/src/configs/defs/sqlite.ts +26 -0
  107. package/templates/hono-server/libs/definition/src/configs/index.ts +1 -0
  108. package/templates/hono-server/oyo.config.json +3 -1
  109. package/templates/hono-server/package.json +2 -3
  110. package/templates/hono-server/pnpm-workspace.yaml +12 -3
  111. package/templates/hono-server/turbo.json +25 -3
  112. package/templates/hono-server/vite.config.ts +1 -0
  113. package/templates/monorepo/.vscode/launch.json +0 -3
  114. package/templates/monorepo/.vscode/settings.json +3 -1
  115. package/templates/monorepo/AGENTS.md +7 -0
  116. package/templates/monorepo/_gitignore +4 -0
  117. package/templates/monorepo/pnpm-workspace.yaml +6 -3
  118. package/templates/monorepo/turbo.json +25 -3
  119. package/templates/monorepo/vite.config.ts +1 -0
  120. package/templates/hono-fullstack/apps/server/src/adapters/nodejs/config.ts +0 -53
  121. package/templates/hono-fullstack/apps/server/src/infra/work-runner/example/runner.test.ts +0 -56
  122. package/templates/hono-fullstack/apps/stack/frontend/modules/example/components/ExamplePage.tsx +0 -59
  123. package/templates/hono-fullstack/apps/stack/nitro/handlers/hono.ts +0 -1
  124. package/templates/hono-fullstack/apps/stack/nitro/plugins/hono.ts +0 -9
  125. package/templates/hono-server/apps/server/src/adapters/nodejs/config.ts +0 -53
  126. package/templates/hono-server/apps/server/src/infra/work-runner/example/runner.test.ts +0 -56
@@ -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 './worker-entry'
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 ExampleWorkRunner implements AppWorkRunner {
25
- static #instance: ExampleWorkRunner | null = null
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('[ExampleWorkRunner] config is required')
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): ExampleWorkRunner {
43
+ public static getInstance(config: WorkerConfig): PiscinaWorkRunner {
44
44
  if (!this.#instance) {
45
- getLogger().info({ worker: config }, 'ExampleWorkRunner initialized')
46
- this.#instance = new ExampleWorkRunner(config)
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(`[ExampleWorkRunner] task name not found: ${taskParams.subject}`)
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 (ExampleWorkRunner.#instance === this) ExampleWorkRunner.#instance = null
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 { MessagePort, Transferable } from 'node:worker_threads'
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: 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: 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(messagePort: MessagePort, value: number, message?: string): void {
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
 
@@ -3,3 +3,7 @@ import * as v from 'valibot'
3
3
  export const processExampleInputSchema = v.object({
4
4
  message: v.pipe(v.string(), v.minLength(1)),
5
5
  })
6
+
7
+ export const createExampleInputSchema = v.object({
8
+ message: v.pipe(v.string(), v.trim(), v.minLength(1), v.maxLength(500)),
9
+ })
@@ -1,13 +1,16 @@
1
- import { Hono } from 'hono'
1
+ import { type Context, Hono } from 'hono'
2
2
  import { describeRoute } from 'hono-openapi'
3
3
  import * as v from 'valibot'
4
+ import { examples, vcExamples, type SqliteDBType } from '@repo/db/sqlite/schema'
4
5
  import type { AppEnv } from '@/shared/app/types'
5
6
  import { WorkSubject } from '@/infra/work-runner/constants'
6
7
  import { E } from '@/shared/errors/defs'
7
8
  import { jsonResponse, OpenApiTags } from '@/shared/network/openapi'
8
9
  import { successJson } from '@/shared/network/utils'
9
10
  import { generalValidator } from '@/shared/network/validator'
10
- import { processExampleInputSchema } from './contracts'
11
+ import { createExampleInputSchema, processExampleInputSchema } from './contracts'
12
+
13
+ const getDb = (context: Context<AppEnv>) => context.get('db') as SqliteDBType
11
14
 
12
15
  export const exampleRoutes = new Hono<AppEnv>()
13
16
  .get(
@@ -20,6 +23,31 @@ export const exampleRoutes = new Hono<AppEnv>()
20
23
  }),
21
24
  (c) => successJson(c, { status: 'ok' }),
22
25
  )
26
+ .get(
27
+ '/examples',
28
+ describeRoute({
29
+ operationId: 'listExamples',
30
+ tags: [OpenApiTags.Example],
31
+ summary: 'List example rows',
32
+ responses: { 200: { description: 'Example rows' } },
33
+ }),
34
+ async (c) => successJson(c, await getDb(c).select(vcExamples.default).from(examples)),
35
+ )
36
+ .post(
37
+ '/examples',
38
+ describeRoute({
39
+ operationId: 'createExample',
40
+ tags: [OpenApiTags.Example],
41
+ summary: 'Create an example row',
42
+ responses: { 200: { description: 'Created example row' } },
43
+ }),
44
+ generalValidator('json', createExampleInputSchema),
45
+ async (c) => {
46
+ const input = c.req.valid('json')
47
+ const [row] = await getDb(c).insert(examples).values(input).returning(vcExamples.default)
48
+ return successJson(c, row)
49
+ },
50
+ )
23
51
  .post(
24
52
  '/examples/process',
25
53
  describeRoute({
@@ -30,10 +58,9 @@ export const exampleRoutes = new Hono<AppEnv>()
30
58
  }),
31
59
  generalValidator('json', processExampleInputSchema),
32
60
  async (c) => {
33
- const input = c.req.valid('json')
34
- const result = await c.get('exampleWorkRunner').run({
61
+ const result = await c.get('workRunner').run({
35
62
  subject: WorkSubject.PROCESS_EXAMPLE,
36
- input,
63
+ input: c.req.valid('json'),
37
64
  })
38
65
  if (!result.ok) {
39
66
  throw E.NORMAL.INTERNAL.create({
@@ -18,19 +18,31 @@ describe('Example worker task', () => {
18
18
  expect.arrayContaining([
19
19
  expect.objectContaining({ type: 'log' }),
20
20
  expect.objectContaining({ type: 'progress', value: 100 }),
21
+ expect.objectContaining({ type: 'result' }),
21
22
  ]),
22
23
  )
23
24
  port1.close()
24
25
  port2.close()
25
26
  })
26
27
 
27
- it('preserves a business exception model', async () => {
28
+ it('serializes a business exception and reports it', async () => {
28
29
  const { port1, port2 } = new MessageChannel()
29
- await expect(executeExampleTask({ message: ' ' }, port2)).rejects.toMatchObject({
30
- name: 'WorkerTaskBusinessError',
31
- message: 'Message must not be empty',
32
- details: { field: 'message' },
30
+ const messages: unknown[] = []
31
+ port1.on('message', (message) => messages.push(message))
32
+ const result = await executeExampleTask({ message: ' ' }, port2)
33
+ await new Promise<void>((resolve) => setImmediate(resolve))
34
+
35
+ expect(result).toEqual({
36
+ ok: false,
37
+ error: {
38
+ code: 'EXAMPLE_PROCESS_FAILED',
39
+ message: 'Message must not be empty',
40
+ details: { field: 'message' },
41
+ },
33
42
  })
43
+ expect(messages).toEqual(
44
+ expect.arrayContaining([expect.objectContaining({ type: 'error' })]),
45
+ )
34
46
  port1.close()
35
47
  port2.close()
36
48
  })
@@ -1,9 +1,15 @@
1
1
  import type { ProcessExampleInput, ProcessExampleOutput } from '@repo/definition/backend'
2
2
  import { type WorkSubject } from '@/infra/work-runner/constants'
3
- import type { DefineRunnerTaskModel, RunnerTaskResult } from '@/infra/work-runner/types'
4
- import { WorkerTaskBusinessError } from '@/infra/work-runner/utils/business-error'
3
+ import type {
4
+ DefineRunnerTaskModel,
5
+ RunnerTaskResult,
6
+ WorkerMessagePort,
7
+ } from '@/infra/work-runner/types'
8
+ import {
9
+ isWorkerTaskBusinessError,
10
+ WorkerTaskBusinessError,
11
+ } from '@/infra/work-runner/utils/business-error'
5
12
  import { workerLog, workerProgress } from '@/infra/work-runner/utils/message'
6
- import type { MessagePort } from 'node:worker_threads'
7
13
 
8
14
  export type ExampleTaskErrorCode = 'EXAMPLE_PROCESS_FAILED'
9
15
 
@@ -16,20 +22,37 @@ export type ExampleRunnerTaskModel = DefineRunnerTaskModel<
16
22
 
17
23
  export async function executeExampleTask(
18
24
  input: ExampleRunnerTaskModel['input'],
19
- messagePort: MessagePort,
25
+ messagePort: WorkerMessagePort,
20
26
  ): Promise<RunnerTaskResult<ProcessExampleOutput, ExampleTaskErrorCode>> {
21
- workerLog(messagePort, 'debug', 'Example task started')
22
- workerProgress(messagePort, 0, 'Validating input')
27
+ try {
28
+ workerLog(messagePort, 'debug', 'Example task started')
29
+ workerProgress(messagePort, 0, 'Validating input')
23
30
 
24
- const original = input.message
25
- const normalized = original.trim().replace(/\s+/g, ' ')
26
- if (!normalized) {
27
- throw new WorkerTaskBusinessError('Message must not be empty', { field: 'message' })
28
- }
31
+ const original = input.message
32
+ const normalized = original.trim().replace(/\s+/g, ' ')
33
+ if (!normalized) {
34
+ throw new WorkerTaskBusinessError('Message must not be empty', { field: 'message' })
35
+ }
36
+
37
+ workerProgress(messagePort, 100, 'Example task completed')
38
+ const result = {
39
+ ok: true,
40
+ data: { original, normalized, length: normalized.length },
41
+ } as const
42
+ messagePort.postMessage({ type: 'result', data: result.data })
43
+ return result
44
+ } catch (error) {
45
+ if (!isWorkerTaskBusinessError(error)) throw error
29
46
 
30
- workerProgress(messagePort, 100, 'Example task completed')
31
- return {
32
- ok: true,
33
- data: { original, normalized, length: normalized.length },
47
+ const result: RunnerTaskResult<ProcessExampleOutput, ExampleTaskErrorCode> = {
48
+ ok: false,
49
+ error: {
50
+ code: 'EXAMPLE_PROCESS_FAILED',
51
+ message: error.message,
52
+ ...(error.details ? { details: error.details } : {}),
53
+ },
54
+ }
55
+ messagePort.postMessage({ type: 'error', error: result.error })
56
+ return result
34
57
  }
35
58
  }
@@ -30,9 +30,8 @@ export type AppConfig = {
30
30
  export type AppDependencies = {
31
31
  appConfig: AppConfig
32
32
  loggerHandler: MiddlewareHandler<{ Variables: { logger: AppLogger } }>
33
- exampleWorkRunner: AppWorkRunner
34
- /** Fullstack injects its SQLite database; standalone intentionally leaves this absent. */
35
- db?: unknown
33
+ workRunner: AppWorkRunner
34
+ db: unknown
36
35
  }
37
36
 
38
37
  export type AppEnv = {
@@ -40,6 +39,6 @@ export type AppEnv = {
40
39
  }
41
40
 
42
41
  export type CreateAppOptions = {
43
- basePath?: string
42
+ serverBasePath: string
44
43
  makeDependencies: (c: Context<AppEnv>) => Promise<AppDependencies> | AppDependencies
45
44
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "@oyolincc/dev-config/tsconfig.bundler.json",
3
3
  "compilerOptions": {
4
+ "types": ["node", "./worker-configuration.d.ts"],
4
5
  "verbatimModuleSyntax": true,
5
6
  "jsx": "react-jsx",
6
7
  "jsxImportSource": "hono/jsx",
@@ -52,14 +52,19 @@ export default defineConfig(() => {
52
52
  pack: {
53
53
  ...packConfig,
54
54
  plugins: [virtualWorkers('pack')],
55
+ deps: {
56
+ neverBundle: ['cloudflare:workers'],
57
+ },
55
58
  entry: {
56
59
  ...(isServe
57
60
  ? {}
58
61
  : {
59
- nodeMain: './src/adapters/nodejs/bootstrap.ts',
62
+ nodejsMain: './src/adapters/nodejs/bootstrap.ts',
60
63
  }),
61
- nodeEntry: './src/adapters/nodejs/entry.ts',
62
- 'workers/example-worker': './src/infra/work-runner/example/worker-entry.ts',
64
+ nodejsEntry: './src/adapters/nodejs/entry.ts',
65
+ cloudflareEntry: './src/adapters/cloudflare/entry.ts',
66
+ // Enable this entry together with PiscinaWorkRunner in the Node.js adapter.
67
+ // 'workers/example-worker': './src/infra/work-runner/example/worker-entry.ts',
63
68
  },
64
69
  // deps: {
65
70
  // neverBundle: [...builtinModules],
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "./node_modules/wrangler/config-schema.json",
3
+ "name": "repo-server-dev",
4
+ "main": "./src/adapters/cloudflare/entry.ts",
5
+ "compatibility_date": "2026-09-23",
6
+ "compatibility_flags": ["nodejs_compat"],
7
+ "dev": {
8
+ "port": 6464,
9
+ "inspector_port": 19230,
10
+ },
11
+ "observability": {
12
+ "logs": {
13
+ "enabled": true,
14
+ "invocation_logs": true,
15
+ },
16
+ "traces": {
17
+ "enabled": false,
18
+ },
19
+ },
20
+ "d1_databases": [
21
+ {
22
+ "binding": "DB",
23
+ "database_name": "repo-db",
24
+ "database_id": "00000000-0000-0000-0000-000000000000",
25
+ "migrations_dir": "../../libs/db/migrations/sqlite-d1",
26
+ "migrations_pattern": "../../libs/db/migrations/sqlite-d1/*/migration.sql",
27
+ },
28
+ ],
29
+ }
@@ -20,9 +20,8 @@ RUN pnpm exec turbo build
20
20
 
21
21
  FROM builder AS packager
22
22
  RUN pnpm --filter=@repo/server --prod --legacy deploy /app/deploy
23
- RUN test -f /app/deploy/dist/nodeMain.mjs && \
24
- test -f /app/deploy/dist/nodeEntry.mjs && \
25
- test -f /app/deploy/dist/workers/example-worker.mjs
23
+ RUN test -f /app/deploy/dist/nodejsMain.mjs && \
24
+ test -f /app/deploy/dist/nodejsEntry.mjs
26
25
 
27
26
  FROM base AS runner
28
27
  ENV NODE_ENV=production
@@ -36,5 +35,4 @@ USER server
36
35
  EXPOSE 6464
37
36
  HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
38
37
  CMD node -e "require('http').get('http://127.0.0.1:6464/health',r=>process.exit(r.statusCode<500?0:1)).on('error',()=>process.exit(1))"
39
- CMD ["node", "--import", "dotenv/config", "dist/nodeMain.mjs"]
40
-
38
+ CMD ["node", "--import", "dotenv/config", "dist/nodejsMain.mjs"]
@@ -1,4 +1,5 @@
1
1
  NODE_PORT=6464
2
+ SERVER_BASE_PATH=/api
2
3
  SERVER_ENV=local
3
4
  LOGGER_DIR=./logs
4
5
  LOGGER_FILE_BASE=app
@@ -9,4 +10,3 @@ CORS_DOMAINS='*'
9
10
  # CONCURRENT_TASKS_PER_WORKER=1
10
11
  # MAX_QUEUE=12
11
12
  # WORKER_TASK_TIMEOUT_MS=30000
12
-
@@ -0,0 +1 @@
1
+ DB_PATH=/absolute/path/to/repo.sqlite
@@ -0,0 +1,3 @@
1
+ CLOUDFLARE_ACCOUNT_ID=
2
+ CLOUDFLARE_DATABASE_ID=
3
+ CLOUDFLARE_TOKEN=
@@ -0,0 +1,22 @@
1
+ import path from 'node:path'
2
+ import { defineConfig } from 'drizzle-kit'
3
+ import { d1SchemaConfig } from '@repo/definition/backend'
4
+
5
+ const configDir = import.meta.dirname
6
+
7
+ const { cloudflareAccountId, cloudflareDatabaseId, cloudflareToken } = d1SchemaConfig.resolve({
8
+ context: process.env,
9
+ matchContextKey: 'constantCase',
10
+ })
11
+
12
+ export default defineConfig({
13
+ dialect: 'sqlite',
14
+ driver: 'd1-http',
15
+ schema: path.join(configDir, '../src/dialects/sqlite/schema/tables.ts'),
16
+ out: path.join(configDir, '../migrations/sqlite-d1'),
17
+ dbCredentials: {
18
+ accountId: cloudflareAccountId,
19
+ databaseId: cloudflareDatabaseId,
20
+ token: cloudflareToken,
21
+ },
22
+ })
@@ -0,0 +1,22 @@
1
+ import path from 'node:path'
2
+ import { defineConfig } from 'drizzle-kit'
3
+ import { ensureDatabaseDirectory } from '../src/dialects/sqlite/utils'
4
+ import { sqliteSchemaConfig } from '@repo/definition/backend'
5
+
6
+ const configDir = import.meta.dirname
7
+
8
+ const { dbPath } = sqliteSchemaConfig.resolve({
9
+ context: process.env,
10
+ matchContextKey: 'constantCase',
11
+ })
12
+
13
+ ensureDatabaseDirectory(dbPath)
14
+
15
+ export default defineConfig({
16
+ dialect: 'sqlite',
17
+ schema: path.join(configDir, '../src/dialects/sqlite/schema/tables.ts'),
18
+ out: path.join(configDir, '../migrations/sqlite'),
19
+ dbCredentials: {
20
+ url: dbPath,
21
+ },
22
+ })
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@repo/db",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "type": "module",
9
+ "exports": {
10
+ "./sqlite/d1-client": "./dist/sqlite/d1-client.mjs",
11
+ "./sqlite/schema": "./dist/sqlite/schema.mjs",
12
+ "./sqlite/sqlite": "./dist/sqlite/sqlite.mjs",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "scripts": {
16
+ "build": "vp pack",
17
+ "dev": "vp pack --watch",
18
+ "test": "vp test",
19
+ "check": "vp check",
20
+ "sqlite:export": "oyo env sqlite -- drizzle-kit export --config=drizzle-kit/sqlite.config.ts",
21
+ "dev:sqlite:push": "oyo env sqlite -- drizzle-kit push --config=drizzle-kit/sqlite.config.ts",
22
+ "dev:sqlite:gen": "oyo env sqlite -- drizzle-kit generate --config=drizzle-kit/sqlite.config.ts",
23
+ "dev:sqlite:migrate": "oyo env sqlite -- drizzle-kit migrate --config=drizzle-kit/sqlite.config.ts",
24
+ "dev:d1:gen": "oyo env sqlite-d1 -- drizzle-kit generate --config=drizzle-kit/sqlite-d1.config.ts",
25
+ "dev:d1:migrate": "oyo env sqlite-d1 -- drizzle-kit migrate --config=drizzle-kit/sqlite-d1.config.ts"
26
+ },
27
+ "dependencies": {
28
+ "@repo/definition": "workspace:*",
29
+ "better-sqlite3": "catalog:db",
30
+ "drizzle-orm": "catalog:db"
31
+ },
32
+ "devDependencies": {
33
+ "@oyolincc/cli": "catalog:",
34
+ "@oyolincc/dev-config": "catalog:",
35
+ "@types/better-sqlite3": "catalog:db",
36
+ "@types/node": "catalog:",
37
+ "drizzle-kit": "catalog:db",
38
+ "tsx": "catalog:",
39
+ "typescript": "catalog:",
40
+ "vite-plus": "catalog:"
41
+ }
42
+ }
@@ -0,0 +1,35 @@
1
+ import Database from 'better-sqlite3'
2
+ import { drizzle } from 'drizzle-orm/better-sqlite3'
3
+ import { relations, type SqliteDBType } from './schema'
4
+ import { ensureDatabaseDirectory } from './utils'
5
+
6
+ export type SqliteDatabase = SqliteDBType
7
+
8
+ export interface SqliteClient {
9
+ db: SqliteDatabase
10
+ close(): void
11
+ }
12
+
13
+ export function createSqliteClient(databaseFile: string): SqliteClient {
14
+ if (!databaseFile) throw new Error('databaseFile must not be empty')
15
+
16
+ ensureDatabaseDirectory(databaseFile)
17
+
18
+ const client = new Database(databaseFile)
19
+ client.pragma('foreign_keys = ON')
20
+ if (databaseFile !== ':memory:') client.pragma('journal_mode = WAL')
21
+
22
+ const db = drizzle({ client, relations })
23
+
24
+ let closed = false
25
+
26
+ return {
27
+ db,
28
+ close() {
29
+ if (!closed) {
30
+ client.close()
31
+ closed = true
32
+ }
33
+ },
34
+ }
35
+ }
@@ -0,0 +1,12 @@
1
+ import { drizzle, type AnyD1Database } from 'drizzle-orm/d1'
2
+ import { relations } from './schema'
3
+
4
+ export type D1Binding = AnyD1Database
5
+
6
+ export function createD1Client(database: D1Binding) {
7
+ if (!database) throw new Error('database must not be empty')
8
+
9
+ const db = drizzle(database, { relations })
10
+
11
+ return { db }
12
+ }
@@ -0,0 +1,37 @@
1
+ import { sql } from 'drizzle-orm'
2
+ import { customType, integer, text } from 'drizzle-orm/sqlite-core'
3
+
4
+ export const idType = customType<{ data: string; driverData: number | bigint }>({
5
+ dataType() {
6
+ return 'integer'
7
+ },
8
+ fromDriver(value) {
9
+ return String(value)
10
+ },
11
+ })
12
+
13
+ export const autoIncrementId = (name: string) =>
14
+ idType(name)
15
+ .primaryKey({ autoIncrement: true })
16
+ .$defaultFn(() => sql`null`)
17
+
18
+ export function timestampMsColumn(name: string) {
19
+ return integer(name, { mode: 'timestamp_ms' })
20
+ .notNull()
21
+ .default(sql`(unixepoch('subsec') * 1000)`)
22
+ }
23
+
24
+ export const updatedAtCol = () => timestampMsColumn('updated_at').$onUpdateFn(() => new Date())
25
+
26
+ /**
27
+ * createdAt 和 updatedAt 是数据库行的创建/修改时间,SQLite 保存毫秒时间戳,代码里使用 Date。
28
+ * syncState.updatedTime 是客户端同步版本号,和这两个时间不是一回事,不要混用。
29
+ */
30
+ export const modifiedTimeCols = () => ({
31
+ createdAt: timestampMsColumn('created_at'),
32
+ updatedAt: updatedAtCol(),
33
+ })
34
+
35
+ export function jsonColumn<T>(name: string) {
36
+ return text(name, { mode: 'json' }).$type<T>()
37
+ }
@@ -0,0 +1,7 @@
1
+ export * from './relations'
2
+ export * from './tables'
3
+ export * from './types'
4
+ export * from './vo'
5
+ export * from '@/shared/types'
6
+
7
+ export { relations } from './relations'