nuxt-cf-jobs 0.0.2 → 0.1.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/dist/module.d.mts +4 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +54 -25
- package/dist/runtime/server/app.d.ts +18 -5
- package/dist/runtime/server/app.js +25 -20
- package/dist/runtime/server/registry.d.ts +0 -6
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
package/dist/module.d.mts
CHANGED
|
@@ -69,5 +69,8 @@ declare module '@nuxt/schema' {
|
|
|
69
69
|
}
|
|
70
70
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
declare function generateRegistryTemplate(options: ModuleOptions, rootDir: string, templateDir: string): Promise<string>;
|
|
73
|
+
declare function generateRegistryTypesTemplate(options: ModuleOptions, rootDir: string, templateDir: string): Promise<string>;
|
|
74
|
+
|
|
75
|
+
export { _default as default, generateRegistryTemplate, generateRegistryTypesTemplate };
|
|
73
76
|
export type { ModuleOptions };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -174,6 +174,11 @@ const module$1 = defineNuxtModule({
|
|
|
174
174
|
write: true,
|
|
175
175
|
getContents: async () => generateRegistryTemplate(options, nuxt.options.rootDir, resolve(nuxt.options.buildDir, "cf-jobs"))
|
|
176
176
|
});
|
|
177
|
+
addTemplate({
|
|
178
|
+
filename: "cf-jobs/registry.d.ts",
|
|
179
|
+
write: true,
|
|
180
|
+
getContents: async () => generateRegistryTypesTemplate(options, nuxt.options.rootDir, resolve(nuxt.options.buildDir, "cf-jobs"))
|
|
181
|
+
});
|
|
177
182
|
nuxt.options.alias[options.registryAlias ?? "#cf-jobs/app"] = registryTemplate.dst;
|
|
178
183
|
nuxt.hooks.hook("builder:watch", (async (_event, path) => {
|
|
179
184
|
if (!isWatchedJobPath(path, options, nuxt.options.rootDir))
|
|
@@ -283,38 +288,21 @@ async function generateRegistryTemplate(options, rootDir, templateDir) {
|
|
|
283
288
|
const files = await resolveJobFiles(options, rootDir);
|
|
284
289
|
assertUniqueGeneratedJobNames(files, options, rootDir);
|
|
285
290
|
const imports = files.map((file, index) => {
|
|
286
|
-
return `import job${index} from ${JSON.stringify(toImportPath(templateDir, file))}`;
|
|
287
|
-
});
|
|
288
|
-
const loaders = files.map((file) => {
|
|
289
|
-
return ` ${JSON.stringify(toJobName(file, options, rootDir))}: () => import(${JSON.stringify(toImportPath(templateDir, file))}).then(m => m.default),`;
|
|
291
|
+
return `import job${index} from ${JSON.stringify(toImportPath(templateDir, file).replace(/\.ts$/, ""))}`;
|
|
290
292
|
});
|
|
291
293
|
const jobItems = files.map((_, index) => `job${index}`).join(", ");
|
|
292
294
|
return [
|
|
293
295
|
"/* This file is generated by nuxt-cf-jobs. Do not edit directly. */",
|
|
294
|
-
|
|
295
|
-
`import { useRuntimeConfig } from '
|
|
296
|
-
`import
|
|
297
|
-
`export type { QueueConsumerOptions } from '#cf-jobs/server'`,
|
|
296
|
+
`// @ts-expect-error - nitropack/runtime is resolved at build time inside Nuxt`,
|
|
297
|
+
`import { useRuntimeConfig } from 'nitropack/runtime'`,
|
|
298
|
+
`import { createCfJobsApp } from 'nuxt-cf-jobs/server'`,
|
|
298
299
|
...imports,
|
|
299
300
|
"",
|
|
300
|
-
"export const jobLoaders = {",
|
|
301
|
-
...loaders,
|
|
302
|
-
"} as const",
|
|
303
301
|
`export const jobs = [${jobItems}] as const`,
|
|
304
|
-
`export const app = createCfJobsApp(jobs, useRuntimeConfig
|
|
305
|
-
"export const jobRegistry = app.jobRegistry",
|
|
306
|
-
"",
|
|
307
|
-
"export type Jobs = typeof jobs",
|
|
308
|
-
"export type JobsByName = JobDefinitionsByNameOfLoaders<typeof jobLoaders>",
|
|
309
|
-
"export type JobName = keyof JobsByName & JobNameOf<Jobs>",
|
|
310
|
-
"export type JobDefinitionOf<Name extends JobName> = JobsByName[Name]",
|
|
311
|
-
"export type QueueName = QueueNameOf<Jobs>",
|
|
312
|
-
"export type JobPayload<Name extends JobName> = JobPayloadOf<JobDefinitionOf<Name>>",
|
|
313
|
-
"export type JobQueue<Name extends JobName> = JobQueueByName<Jobs, Name>",
|
|
314
|
-
"export type JobMessage<Name extends JobName> = JobMessageByName<Jobs, Name>",
|
|
315
|
-
"export type QueueMessage<Queue extends QueueName> = JobMessageByQueue<Jobs, Queue>",
|
|
302
|
+
`export const app = createCfJobsApp(jobs, { useRuntimeConfig, defaultQueue: ${options.defaultQueue ? JSON.stringify(options.defaultQueue) : "undefined"} })`,
|
|
316
303
|
"",
|
|
317
304
|
"export const {",
|
|
305
|
+
" jobRegistry,",
|
|
318
306
|
" getHandler,",
|
|
319
307
|
" getJobDefinition,",
|
|
320
308
|
" getJobQueue,",
|
|
@@ -330,6 +318,47 @@ async function generateRegistryTemplate(options, rootDir, templateDir) {
|
|
|
330
318
|
""
|
|
331
319
|
].join("\n");
|
|
332
320
|
}
|
|
321
|
+
async function generateRegistryTypesTemplate(options, rootDir, templateDir) {
|
|
322
|
+
const files = await resolveJobFiles(options, rootDir);
|
|
323
|
+
assertUniqueGeneratedJobNames(files, options, rootDir);
|
|
324
|
+
const jobTypeLines = files.map((file) => {
|
|
325
|
+
return ` typeof import(${JSON.stringify(toImportPath(templateDir, file).replace(/\.ts$/, ""))})['default'],`;
|
|
326
|
+
});
|
|
327
|
+
return [
|
|
328
|
+
"/* This file is generated by nuxt-cf-jobs. Do not edit directly. */",
|
|
329
|
+
`import type { JobMessageByName, JobMessageByQueue, JobNameOf, JobPayloadByName, JobPayloadOf, JobQueueByName, QueueNameOf, QueueConsumerOptions } from 'nuxt-cf-jobs/server'`,
|
|
330
|
+
`export type { QueueConsumerOptions }`,
|
|
331
|
+
"",
|
|
332
|
+
"export declare const jobs: readonly [",
|
|
333
|
+
...jobTypeLines,
|
|
334
|
+
"]",
|
|
335
|
+
"export declare const app: ReturnType<typeof import('nuxt-cf-jobs/server').createCfJobsApp<typeof jobs>>",
|
|
336
|
+
"",
|
|
337
|
+
"export type Jobs = typeof jobs",
|
|
338
|
+
"export type JobsByName = { readonly [Job in Jobs[number] as Job['name']]: Job }",
|
|
339
|
+
"export type JobName = keyof JobsByName & JobNameOf<Jobs>",
|
|
340
|
+
"export type JobDefinitionOf<Name extends JobName> = JobsByName[Name]",
|
|
341
|
+
"export type QueueName = QueueNameOf<Jobs>",
|
|
342
|
+
"export type JobPayload<Name extends JobName> = JobPayloadOf<JobDefinitionOf<Name>>",
|
|
343
|
+
"export type JobQueue<Name extends JobName> = JobQueueByName<Jobs, Name>",
|
|
344
|
+
"export type JobMessage<Name extends JobName> = JobMessageByName<Jobs, Name>",
|
|
345
|
+
"export type QueueMessage<Queue extends QueueName> = JobMessageByQueue<Jobs, Queue>",
|
|
346
|
+
"",
|
|
347
|
+
"export declare const jobRegistry: typeof app.jobRegistry",
|
|
348
|
+
"export declare const getHandler: typeof app.getHandler",
|
|
349
|
+
"export declare const getJobDefinition: typeof app.getJobDefinition",
|
|
350
|
+
"export declare const getJobQueue: typeof app.getJobQueue",
|
|
351
|
+
"export declare const getJobRoute: typeof app.getJobRoute",
|
|
352
|
+
"export declare const validateRegistry: typeof app.validateRegistry",
|
|
353
|
+
"export declare const validateQueueBindings: typeof app.validateQueueBindings",
|
|
354
|
+
"export declare const assertQueueBindings: typeof app.assertQueueBindings",
|
|
355
|
+
"export declare const getQueue: typeof app.getQueue",
|
|
356
|
+
"export declare const buildJobPayload: typeof app.buildJobPayload",
|
|
357
|
+
"export declare const prepareJob: typeof app.prepareJob",
|
|
358
|
+
"export declare const registerQueueConsumer: typeof app.registerQueueConsumer",
|
|
359
|
+
""
|
|
360
|
+
].join("\n");
|
|
361
|
+
}
|
|
333
362
|
async function resolveJobFiles(options, rootDir) {
|
|
334
363
|
const dirs = toArray(options.jobsDir ?? "server/jobs");
|
|
335
364
|
const pattern = options.jobsPattern ?? "**/*.ts";
|
|
@@ -365,7 +394,7 @@ function toArray(value) {
|
|
|
365
394
|
return Array.isArray(value) ? value : [value];
|
|
366
395
|
}
|
|
367
396
|
function toImportPath(fromDir, file) {
|
|
368
|
-
const path = relative(fromDir, file).replace(/\\/g, "/")
|
|
397
|
+
const path = relative(fromDir, file).replace(/\\/g, "/");
|
|
369
398
|
return path.startsWith(".") ? path : `./${path}`;
|
|
370
399
|
}
|
|
371
400
|
function toJobName(file, options, rootDir) {
|
|
@@ -377,4 +406,4 @@ function toJobName(file, options, rootDir) {
|
|
|
377
406
|
return path.replace(/\/index$/, "");
|
|
378
407
|
}
|
|
379
408
|
|
|
380
|
-
export { module$1 as default };
|
|
409
|
+
export { module$1 as default, generateRegistryTemplate, generateRegistryTypesTemplate };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyJobDefinition, JobNameOf, JobPayloadByName
|
|
1
|
+
import type { AnyJobDefinition, JobNameOf, JobPayloadByName } from './registry.js';
|
|
2
2
|
import type { PrepareRegisteredDurableJobOptions } from './outbox.js';
|
|
3
3
|
import type { QueueSource, RegisterRegisteredQueueConsumerOptions } from './queue.js';
|
|
4
4
|
import type { QueueBindingsConfig } from './types.js';
|
|
@@ -9,11 +9,24 @@ export interface CfJobsRuntimeConfig {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
export type CfJobsQueueConsumerOptions<Env extends Record<string, unknown>, Db, Logger> = Omit<RegisterRegisteredQueueConsumerOptions<Env, Db, Logger>, 'registry' | 'queues'>;
|
|
12
|
+
export type UseRuntimeConfigFn = (event?: unknown) => CfJobsRuntimeConfig;
|
|
12
13
|
export interface CreateCfJobsAppOptions {
|
|
14
|
+
/** Bundled nitro's `useRuntimeConfig`. Required — tests can pass a stub. */
|
|
15
|
+
useRuntimeConfig: UseRuntimeConfigFn;
|
|
13
16
|
/** Fallback queue applied to jobs whose `defineJob` omits `queue`. */
|
|
14
17
|
defaultQueue?: string;
|
|
15
18
|
}
|
|
16
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Builds the registry + helpers around a statically-known array of jobs. The
|
|
21
|
+
* generated `#cf-jobs/app` template imports each job source file directly and
|
|
22
|
+
* passes the resulting array in — rollup resolves nuxt `#aliases` and
|
|
23
|
+
* extensionless paths inside the bundle.
|
|
24
|
+
*
|
|
25
|
+
* `useRuntimeConfig` is injected so this module never imports `nitropack/runtime`
|
|
26
|
+
* itself; that keeps `app.ts` usable from unit tests / non-nitro consumers and
|
|
27
|
+
* avoids `#nitro-internal-virtual/*` pulling into anything that isn't bundled.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createCfJobsApp<const Jobs extends readonly AnyJobDefinition[]>(jobs: Jobs, { useRuntimeConfig, defaultQueue }: CreateCfJobsAppOptions): {
|
|
17
30
|
jobs: Jobs;
|
|
18
31
|
jobRegistry: {
|
|
19
32
|
jobs: Jobs;
|
|
@@ -26,7 +39,7 @@ export declare function createCfJobsApp<const Jobs extends readonly AnyJobDefini
|
|
|
26
39
|
(name: string): AnyJobDefinition | undefined;
|
|
27
40
|
};
|
|
28
41
|
getJobQueue: {
|
|
29
|
-
<Name extends JobNameOf<Jobs>>(name: Name): JobQueueByName<Jobs, Name> | undefined;
|
|
42
|
+
<Name extends JobNameOf<Jobs>>(name: Name): import("./registry.js").JobQueueByName<Jobs, Name> | undefined;
|
|
30
43
|
(name: string): string | undefined;
|
|
31
44
|
};
|
|
32
45
|
buildPayload<Name extends JobNameOf<Jobs>>(name: Name, payload: JobPayloadByName<Jobs, Name>): {
|
|
@@ -49,7 +62,7 @@ export declare function createCfJobsApp<const Jobs extends readonly AnyJobDefini
|
|
|
49
62
|
(name: string): AnyJobDefinition | undefined;
|
|
50
63
|
};
|
|
51
64
|
getJobQueue: {
|
|
52
|
-
<Name extends JobNameOf<Jobs>>(name: Name): JobQueueByName<Jobs, Name> | undefined;
|
|
65
|
+
<Name extends JobNameOf<Jobs>>(name: Name): import("./registry.js").JobQueueByName<Jobs, Name> | undefined;
|
|
53
66
|
(name: string): string | undefined;
|
|
54
67
|
};
|
|
55
68
|
getJobRoute: (name: string) => {
|
|
@@ -69,7 +82,7 @@ export declare function createCfJobsApp<const Jobs extends readonly AnyJobDefini
|
|
|
69
82
|
buildJobPayload: <Name extends JobNameOf<Jobs>>(name: Name, payload: JobPayloadByName<Jobs, Name>) => {
|
|
70
83
|
_task: Name;
|
|
71
84
|
} & JobPayloadByName<Jobs, Name>;
|
|
72
|
-
prepareJob: <Name extends JobNameOf<Jobs>>(opts: PrepareRegisteredDurableJobOptions<Jobs, Name>) => Promise<import("./outbox.js").DurableJobRecord<JobQueueByName<Jobs, Name>>>;
|
|
85
|
+
prepareJob: <Name extends JobNameOf<Jobs>>(opts: PrepareRegisteredDurableJobOptions<Jobs, Name>) => Promise<import("./outbox.js").DurableJobRecord<import("./registry.js").JobQueueByName<Jobs, Name>>>;
|
|
73
86
|
registerQueueConsumer: <Env extends Record<string, unknown>, Db, Logger>(nitroApp: {
|
|
74
87
|
hooks: {
|
|
75
88
|
hook: (name: any, handler: any) => void;
|
|
@@ -3,16 +3,21 @@ import { prepareRegisteredDurableJob } from "./outbox.js";
|
|
|
3
3
|
import {
|
|
4
4
|
assertJobQueueBindings,
|
|
5
5
|
createJobQueue,
|
|
6
|
-
|
|
6
|
+
processRegisteredQueueBatch,
|
|
7
7
|
resolveNitroTaskEnv,
|
|
8
8
|
validateJobQueueBindings,
|
|
9
9
|
validateQueueBindingShape,
|
|
10
10
|
validateQueueConsumerConfig
|
|
11
11
|
} from "./queue.js";
|
|
12
12
|
import { validateJobDefinitions } from "./registry.js";
|
|
13
|
-
export function createCfJobsApp(jobs, useRuntimeConfig,
|
|
14
|
-
const
|
|
15
|
-
const jobRegistry = defineJobRegistry(
|
|
13
|
+
export function createCfJobsApp(jobs, { useRuntimeConfig, defaultQueue }) {
|
|
14
|
+
const materialized = defaultQueue ? jobs.map((j) => j.queue ? j : { ...j, queue: defaultQueue }) : jobs.slice();
|
|
15
|
+
const jobRegistry = defineJobRegistry(materialized);
|
|
16
|
+
const jobIssues = validateJobDefinitions(materialized);
|
|
17
|
+
if (jobIssues.length > 0) {
|
|
18
|
+
console.warn(`[nuxt-cf-jobs] job definition warnings:
|
|
19
|
+
${jobIssues.map((i) => ` - [job:${i.name}] ${i.reason}`).join("\n")}`);
|
|
20
|
+
}
|
|
16
21
|
function getQueue(sourceOrJob, maybeJob) {
|
|
17
22
|
const isJobOnly = maybeJob === void 0 && isJobDefinition(sourceOrJob);
|
|
18
23
|
const job = isJobOnly ? sourceOrJob : maybeJob;
|
|
@@ -30,38 +35,38 @@ export function createCfJobsApp(jobs, useRuntimeConfig, appOpts = {}) {
|
|
|
30
35
|
function prepareJob(opts) {
|
|
31
36
|
return prepareRegisteredDurableJob(jobRegistry, opts);
|
|
32
37
|
}
|
|
33
|
-
|
|
34
|
-
function logStartupWarnings(queues) {
|
|
35
|
-
if (startupLogged)
|
|
36
|
-
return;
|
|
37
|
-
startupLogged = true;
|
|
38
|
+
function logQueueWarnings(queues) {
|
|
38
39
|
const issues = [];
|
|
39
|
-
for (const issue of validateJobDefinitions(effectiveJobs))
|
|
40
|
-
issues.push(`[job:${issue.name}] ${issue.reason}`);
|
|
41
40
|
for (const issue of validateQueueBindingShape(queues))
|
|
42
41
|
issues.push(`[queue:${issue.queue}] ${issue.reason}: ${issue.detail}`);
|
|
43
|
-
for (const issue of validateJobQueueBindings(queues,
|
|
42
|
+
for (const issue of validateJobQueueBindings(queues, materialized))
|
|
44
43
|
issues.push(`[job:${issue.jobName}] missing binding for queue "${issue.queue}"`);
|
|
45
|
-
for (const issue of validateQueueConsumerConfig(queues,
|
|
44
|
+
for (const issue of validateQueueConsumerConfig(queues, materialized))
|
|
46
45
|
issues.push(`[job:${issue.jobName ?? "?"}@${issue.queue}] ${issue.reason}: ${issue.detail}`);
|
|
47
46
|
if (issues.length === 0)
|
|
48
47
|
return;
|
|
49
|
-
console.warn(`[nuxt-cf-jobs]
|
|
48
|
+
console.warn(`[nuxt-cf-jobs] queue config warnings:
|
|
50
49
|
${issues.map((i) => ` - ${i}`).join("\n")}`);
|
|
51
50
|
}
|
|
52
51
|
function registerQueueConsumer(nitroApp, opts) {
|
|
53
|
-
const
|
|
54
|
-
logStartupWarnings(queues);
|
|
55
|
-
return registerRegisteredQueueConsumer(nitroApp, {
|
|
52
|
+
const ready = {
|
|
56
53
|
...opts,
|
|
57
54
|
registry: jobRegistry,
|
|
58
55
|
queues: () => useRuntimeConfig().cfJobs.queues
|
|
56
|
+
};
|
|
57
|
+
let warned = false;
|
|
58
|
+
nitroApp.hooks.hook("cloudflare:queue", async (payload) => {
|
|
59
|
+
if (!warned) {
|
|
60
|
+
warned = true;
|
|
61
|
+
logQueueWarnings(useRuntimeConfig().cfJobs.queues);
|
|
62
|
+
}
|
|
63
|
+
await processRegisteredQueueBatch(payload, ready);
|
|
59
64
|
});
|
|
60
65
|
}
|
|
61
|
-
const validateQueueBindings = (queues = useRuntimeConfig().cfJobs.queues) => validateJobQueueBindings(queues,
|
|
62
|
-
const assertQueueBindings = (queues = useRuntimeConfig().cfJobs.queues) => assertJobQueueBindings(queues,
|
|
66
|
+
const validateQueueBindings = (queues = useRuntimeConfig().cfJobs.queues) => validateJobQueueBindings(queues, materialized);
|
|
67
|
+
const assertQueueBindings = (queues = useRuntimeConfig().cfJobs.queues) => assertJobQueueBindings(queues, materialized);
|
|
63
68
|
return {
|
|
64
|
-
jobs:
|
|
69
|
+
jobs: materialized,
|
|
65
70
|
jobRegistry,
|
|
66
71
|
getHandler: jobRegistry.getHandler,
|
|
67
72
|
getJobDefinition: jobRegistry.getJobDefinition,
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import type { JobBackoff, JobDefinition, JobFailedHandler, JobHandler, JobMiddleware, JobPayloadSchema } from './types.js';
|
|
2
2
|
export type JobPayloadMap = Record<string, unknown>;
|
|
3
3
|
export type AnyJobDefinition = JobDefinition<string, any, string, any, any, any>;
|
|
4
|
-
export type JobDefinitionLoader<Job extends AnyJobDefinition = AnyJobDefinition> = () => Promise<Job>;
|
|
5
|
-
export type JobDefinitionLoaderMap = Record<string, JobDefinitionLoader>;
|
|
6
|
-
export type JobDefinitionOfLoader<Loader extends JobDefinitionLoader> = Awaited<ReturnType<Loader>>;
|
|
7
|
-
export type JobDefinitionsByNameOfLoaders<Loaders extends JobDefinitionLoaderMap> = {
|
|
8
|
-
readonly [Name in keyof Loaders]: Loaders[Name] extends JobDefinitionLoader<infer Job> ? Job : never;
|
|
9
|
-
};
|
|
10
4
|
export type JobPayloadOf<Job extends AnyJobDefinition> = Job extends JobDefinition<string, infer Payload, string, any, any, any> ? Payload extends object ? Payload : never : never;
|
|
11
5
|
export type JobMessageOf<Job extends AnyJobDefinition> = Job extends JobDefinition<infer Name, any, string, any, any, any> ? {
|
|
12
6
|
_task: Name;
|
package/dist/types.d.mts
CHANGED