@strav/cli 0.4.18 → 0.4.25
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 +6 -6
- package/src/cli/bootstrap.ts +17 -3
- package/src/cli/index.ts +1 -1
- package/src/commands/queue_work.ts +69 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strav/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI framework and code generators for the Strav framework",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"strav": "./src/cli/strav.ts"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@strav/kernel": "0.4.
|
|
38
|
-
"@strav/http": "0.4.
|
|
39
|
-
"@strav/database": "0.4.
|
|
40
|
-
"@strav/queue": "0.4.
|
|
41
|
-
"@strav/signal": "0.4.
|
|
37
|
+
"@strav/kernel": "0.4.25",
|
|
38
|
+
"@strav/http": "0.4.25",
|
|
39
|
+
"@strav/database": "0.4.25",
|
|
40
|
+
"@strav/queue": "0.4.25",
|
|
41
|
+
"@strav/signal": "0.4.25"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"chalk": "^5.6.2",
|
package/src/cli/bootstrap.ts
CHANGED
|
@@ -41,12 +41,23 @@ export async function shutdown(db: Database): Promise<void> {
|
|
|
41
41
|
await db.close()
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export interface WithProvidersOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Install SIGINT/SIGTERM handlers for graceful shutdown. Default: `true`.
|
|
47
|
+
* Set to `false` when the caller owns signal handling and must control
|
|
48
|
+
* shutdown ordering — e.g. `queue:work`, which lets the worker drain its
|
|
49
|
+
* in-flight job before `app.shutdown()` tears down providers.
|
|
50
|
+
*/
|
|
51
|
+
signalHandlers?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
/**
|
|
45
55
|
* Bootstrap an Application with the given service providers.
|
|
46
56
|
*
|
|
47
57
|
* Creates a fresh Application, registers all providers, boots them
|
|
48
58
|
* in dependency order, and returns the running application.
|
|
49
|
-
* Signal handlers for graceful shutdown are installed automatically
|
|
59
|
+
* Signal handlers for graceful shutdown are installed automatically
|
|
60
|
+
* unless `options.signalHandlers` is `false`.
|
|
50
61
|
*
|
|
51
62
|
* @example
|
|
52
63
|
* const app = await withProviders([
|
|
@@ -55,9 +66,12 @@ export async function shutdown(db: Database): Promise<void> {
|
|
|
55
66
|
* new AuthProvider({ resolver: (id) => User.find(id) }),
|
|
56
67
|
* ])
|
|
57
68
|
*/
|
|
58
|
-
export async function withProviders(
|
|
69
|
+
export async function withProviders(
|
|
70
|
+
providers: ServiceProvider[],
|
|
71
|
+
options?: WithProvidersOptions,
|
|
72
|
+
): Promise<Application> {
|
|
59
73
|
const app = new Application()
|
|
60
74
|
for (const provider of providers) app.use(provider)
|
|
61
|
-
await app.start()
|
|
75
|
+
await app.start(options)
|
|
62
76
|
return app
|
|
63
77
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import type { Command } from 'commander'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
|
-
import
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { bootstrap, shutdown, withProviders } from '../cli/bootstrap.ts'
|
|
5
|
+
import type Application from '@strav/kernel/core/application'
|
|
6
|
+
import type ServiceProvider from '@strav/kernel/core/service_provider'
|
|
7
|
+
import type Database from '@strav/database/database/database'
|
|
4
8
|
import Queue from '@strav/queue/queue/queue'
|
|
9
|
+
import QueueProvider from '@strav/queue/providers/queue_provider'
|
|
5
10
|
import Worker from '@strav/queue/queue/worker'
|
|
6
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Conventional file the worker loads on start. Its top-level `Queue.handle(...)`
|
|
14
|
+
* calls register the job handlers, and its `providers` export lists the service
|
|
15
|
+
* providers the worker boots — mirroring `start/providers.ts` for the web entry.
|
|
16
|
+
*/
|
|
17
|
+
const JOBS_FILE = 'start/jobs.ts'
|
|
18
|
+
|
|
7
19
|
export function register(program: Command): void {
|
|
8
20
|
program
|
|
9
21
|
.command('queue:work')
|
|
@@ -11,19 +23,67 @@ export function register(program: Command): void {
|
|
|
11
23
|
.option('--queue <name>', 'Queue to process', 'default')
|
|
12
24
|
.option('--sleep <ms>', 'Poll interval in milliseconds', '1000')
|
|
13
25
|
.action(async options => {
|
|
14
|
-
|
|
26
|
+
const queue = options.queue
|
|
27
|
+
const sleep = parseInt(options.sleep)
|
|
28
|
+
|
|
29
|
+
let app: Application | undefined
|
|
30
|
+
let db: Database | undefined
|
|
31
|
+
|
|
15
32
|
try {
|
|
16
|
-
const
|
|
17
|
-
|
|
33
|
+
const jobsPath = path.resolve(JOBS_FILE)
|
|
34
|
+
|
|
35
|
+
if (await Bun.file(jobsPath).exists()) {
|
|
36
|
+
// Importing start/jobs.ts runs its top-level Queue.handle(...) calls
|
|
37
|
+
// and exposes the provider list the worker needs. Booting those
|
|
38
|
+
// providers wires the facades (mail, notification, …) that handlers
|
|
39
|
+
// depend on — bootstrap() alone leaves them unregistered.
|
|
40
|
+
const jobs = await import(jobsPath)
|
|
41
|
+
const providers: ServiceProvider[] = Array.isArray(jobs.providers)
|
|
42
|
+
? [...jobs.providers]
|
|
43
|
+
: []
|
|
44
|
+
|
|
45
|
+
if (providers.length === 0) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`${JOBS_FILE} must export a \`providers\` array — the service ` +
|
|
48
|
+
`providers the worker boots (ConfigProvider, DatabaseProvider, ` +
|
|
49
|
+
`QueueProvider, plus any facade providers your handlers use).`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// QueueProvider wires the Queue singleton and ensures the tables —
|
|
54
|
+
// add it if the app's provider list left it out.
|
|
55
|
+
if (!providers.some(p => p.name === 'queue')) {
|
|
56
|
+
providers.push(new QueueProvider())
|
|
57
|
+
}
|
|
18
58
|
|
|
19
|
-
|
|
20
|
-
|
|
59
|
+
// signalHandlers: false — the Worker installs its own SIGINT/SIGTERM
|
|
60
|
+
// handlers that drain the in-flight job before start() returns. We
|
|
61
|
+
// call app.shutdown() only afterwards (in finally), so providers are
|
|
62
|
+
// never torn down while a job is still running.
|
|
63
|
+
app = await withProviders(providers, { signalHandlers: false })
|
|
64
|
+
} else {
|
|
65
|
+
console.warn(
|
|
66
|
+
chalk.yellow(`No ${JOBS_FILE} found — the worker has no registered handlers.`),
|
|
67
|
+
)
|
|
68
|
+
console.warn(
|
|
69
|
+
chalk.dim(
|
|
70
|
+
` Create ${JOBS_FILE} with your Queue.handle(...) calls and a ` +
|
|
71
|
+
`\`providers\` export, or every dispatched job will fail.`,
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
const { db: database, config } = await bootstrap()
|
|
75
|
+
db = database
|
|
76
|
+
new Queue(db, config)
|
|
77
|
+
await Queue.ensureTables()
|
|
78
|
+
}
|
|
21
79
|
|
|
22
|
-
const
|
|
23
|
-
const sleep = parseInt(options.sleep)
|
|
80
|
+
const handlers = [...Queue.handlers.keys()]
|
|
24
81
|
|
|
25
82
|
console.log(chalk.cyan(`Worker starting on queue "${queue}"...`))
|
|
26
83
|
console.log(chalk.dim(` poll interval: ${sleep}ms`))
|
|
84
|
+
console.log(
|
|
85
|
+
chalk.dim(` handlers: ${handlers.length > 0 ? handlers.join(', ') : 'none'}`),
|
|
86
|
+
)
|
|
27
87
|
console.log(chalk.dim(' Press Ctrl+C to stop.\n'))
|
|
28
88
|
|
|
29
89
|
const worker = new Worker({ queue, sleep })
|
|
@@ -34,6 +94,7 @@ export function register(program: Command): void {
|
|
|
34
94
|
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : err}`))
|
|
35
95
|
process.exit(1)
|
|
36
96
|
} finally {
|
|
97
|
+
if (app) await app.shutdown()
|
|
37
98
|
if (db) await shutdown(db)
|
|
38
99
|
}
|
|
39
100
|
})
|