@dunx/create-app 1.2.0 → 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/package.json +1 -1
- package/templates/features/docs/docs.demo.ts +6 -1
- package/templates/features/jobs/jobs.controller.ts +1 -1
- package/templates/features/jobs/jobs.module.ts +13 -3
- package/templates/features/jobs/jobs.processor.ts +37 -0
- package/templates/features/jobs/thumbnail.jobs.ts +5 -3
package/package.json
CHANGED
|
@@ -80,9 +80,14 @@ export class DocsDemo {
|
|
|
80
80
|
// markup, with both script bodies removed. Inside a <script> everything is
|
|
81
81
|
// text, and minified React's own string table contains `src=` and `<script`.
|
|
82
82
|
const shell = html.replace(/(<script[^>]*>)[\s\S]*?(<\/script>)/g, '$1$2');
|
|
83
|
+
// A `<link>` counts only if it would actually fetch. The page carries one
|
|
84
|
+
// for its favicon, as a `data:` URI, which the browser never requests.
|
|
85
|
+
const fetchedLink = [...shell.matchAll(/<link\b[^>]*href="([^"]*)"/g)].some(
|
|
86
|
+
([, href]) => href !== undefined && !href.startsWith('data:'),
|
|
87
|
+
);
|
|
83
88
|
const external =
|
|
84
89
|
/\ssrc=/.test(shell) ||
|
|
85
|
-
|
|
90
|
+
fetchedLink ||
|
|
86
91
|
/url\(\s*["']?(https?:)?\/\//.test(html) ||
|
|
87
92
|
html.includes('//cdn');
|
|
88
93
|
logger.info(
|
|
@@ -26,7 +26,7 @@ const oneJob = { params: z.object({ id: z.string().min(1) }) } as const;
|
|
|
26
26
|
/**
|
|
27
27
|
* The publish side. Nothing here consumes: `QueueModule.forRoot` binds
|
|
28
28
|
* `JobPublisher` and no worker, so this process enqueues and returns immediately.
|
|
29
|
-
*
|
|
29
|
+
* Consumed by this same process - see `JobsModule`'s `consume: true`.
|
|
30
30
|
*/
|
|
31
31
|
@Controller('jobs')
|
|
32
32
|
export class JobsController {
|
|
@@ -10,10 +10,13 @@ import { ThumbnailJobs } from './thumbnail.jobs.js';
|
|
|
10
10
|
* process publishes, a separate worker process consumes, and they agree only on
|
|
11
11
|
* this module.
|
|
12
12
|
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
13
|
+
* `consume: true` is what makes this process work them as well as publish, and it
|
|
14
|
+
* is the only line about it anywhere - the container owns starting and stopping the
|
|
15
|
+
* workers, so no entrypoint has to. Leave it out and the module binds the publish
|
|
16
|
+
* side alone, which is what a web tier with a separate worker fleet wants.
|
|
17
|
+
*
|
|
15
18
|
* `PicturesModule` is here because the handler injects `Thumbnails`, and the
|
|
16
|
-
*
|
|
19
|
+
* container that runs it has to be able to build it.
|
|
17
20
|
*/
|
|
18
21
|
@Module({
|
|
19
22
|
imports: [
|
|
@@ -23,6 +26,13 @@ import { ThumbnailJobs } from './thumbnail.jobs.js';
|
|
|
23
26
|
return {
|
|
24
27
|
...(url === undefined ? {} : { url }),
|
|
25
28
|
prefix: 'dunx-full',
|
|
29
|
+
// This process works its own queues. The container starts the workers at
|
|
30
|
+
// onInit and stops them at onShutdown - before the database they use -
|
|
31
|
+
// so `main.ts` says nothing about queues and there is no second command.
|
|
32
|
+
consume: true,
|
|
33
|
+
// The file bullmq forks into for a queue whose handler is marked
|
|
34
|
+
// `background`. Absolute, because the child resolves it, not this module.
|
|
35
|
+
processor: new URL('./jobs.processor.ts', import.meta.url).pathname,
|
|
26
36
|
};
|
|
27
37
|
},
|
|
28
38
|
inject: [AppConfigService] as const,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { JobProcessor } from '@dunx/infra/queue';
|
|
2
|
+
import { ConfigModule, Module } from '@dunx/core';
|
|
3
|
+
import { LoggerModule } from '@dunx/infra/logger';
|
|
4
|
+
import { AppConfigService, validate } from '../config.js';
|
|
5
|
+
import { JobsModule } from './jobs.module.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* **The file bullmq forks into.** Its default export is the processor, and nothing
|
|
9
|
+
* else here runs in the parent.
|
|
10
|
+
*
|
|
11
|
+
* The child builds its own container, which is the whole point: a handler gets the
|
|
12
|
+
* database, the image pipeline and the logger it declares, without sharing an event
|
|
13
|
+
* loop with the process serving HTTP. `JobProcessor` builds it once per child and
|
|
14
|
+
* reuses it for every job on that child.
|
|
15
|
+
*
|
|
16
|
+
* Its own module rather than reusing `WorkerModule` from `worker.ts`: that file is
|
|
17
|
+
* an entrypoint with a `run()` at the bottom, and importing it here would boot a
|
|
18
|
+
* second worker inside every child.
|
|
19
|
+
*/
|
|
20
|
+
@Module({
|
|
21
|
+
imports: [
|
|
22
|
+
ConfigModule.forRoot({ validate, as: AppConfigService }),
|
|
23
|
+
LoggerModule.forRootAsync({
|
|
24
|
+
useFactory: (config: AppConfigService) => ({
|
|
25
|
+
// Named so a line from a child is attributable to one on sight - which is
|
|
26
|
+
// the traceability a sandbox is for.
|
|
27
|
+
name: `${config.get('appName')}-job`,
|
|
28
|
+
level: config.get('log').level,
|
|
29
|
+
}),
|
|
30
|
+
inject: [AppConfigService] as const,
|
|
31
|
+
}),
|
|
32
|
+
JobsModule,
|
|
33
|
+
],
|
|
34
|
+
})
|
|
35
|
+
class JobProcessorModule {}
|
|
36
|
+
|
|
37
|
+
export default new JobProcessor(JobProcessorModule).handle;
|
|
@@ -32,7 +32,10 @@ export class ThumbnailJobs {
|
|
|
32
32
|
private readonly logger: Logger,
|
|
33
33
|
) {}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// `background: true` puts this queue's jobs in a forked child: a slow or
|
|
36
|
+
// crashing render cannot take the server with it, and its log lines still land
|
|
37
|
+
// in this process's stream.
|
|
38
|
+
@JobHandler({ queue: THUMBNAIL_QUEUE, name: 'render', background: true })
|
|
36
39
|
async render(job: Job<RenderRequest>): Promise<RenderResult> {
|
|
37
40
|
const encoded = await this.thumbnails.render({
|
|
38
41
|
width: job.data.width,
|
|
@@ -45,8 +48,7 @@ export class ThumbnailJobs {
|
|
|
45
48
|
height: encoded.height,
|
|
46
49
|
bytes: encoded.bytes.byteLength,
|
|
47
50
|
};
|
|
48
|
-
// Written
|
|
49
|
-
// did not run in the web process.
|
|
51
|
+
// Written in the child, and visible here: that is the point of the sandbox.
|
|
50
52
|
this.logger.info(`rendered job ${job.id ?? '?'}`, result);
|
|
51
53
|
return result;
|
|
52
54
|
}
|