@momentumcms/core 0.5.0 → 0.5.2
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/src/generators/generator.cjs +8 -4
- package/src/generators/generator.js +8 -4
- package/src/index.cjs +36 -1
- package/src/index.d.ts +2 -0
- package/src/index.js +36 -1
- package/src/lib/collections/collection.types.d.ts +5 -0
- package/src/lib/collections/media.collection.d.ts +15 -0
- package/src/lib/cron/index.d.ts +29 -0
- package/src/lib/fields/field.types.d.ts +2 -2
- package/src/lib/plugins.d.ts +7 -1
- package/src/lib/queue/index.d.ts +208 -0
- package/src/lib/storage/index.d.ts +54 -4
package/package.json
CHANGED
|
@@ -711,7 +711,9 @@ function generateAdminConfig(config, typesRelPath) {
|
|
|
711
711
|
}
|
|
712
712
|
lines.push(`import type { ${typeImports.join(", ")} } from '${typesRelPath}';`);
|
|
713
713
|
for (const plugin of pluginsWithAdminRoutes) {
|
|
714
|
-
const imp = plugin.browserImports
|
|
714
|
+
const imp = plugin.browserImports?.adminRoutes;
|
|
715
|
+
if (!imp)
|
|
716
|
+
continue;
|
|
715
717
|
lines.push(`import { ${imp.exportName} } from '${imp.path}';`);
|
|
716
718
|
}
|
|
717
719
|
lines.push("");
|
|
@@ -744,9 +746,11 @@ ${globalItems},
|
|
|
744
746
|
}
|
|
745
747
|
}
|
|
746
748
|
if (pluginsWithAdminRoutes.length > 0) {
|
|
747
|
-
const pluginItems = pluginsWithAdminRoutes.
|
|
748
|
-
const imp = p.browserImports
|
|
749
|
-
|
|
749
|
+
const pluginItems = pluginsWithAdminRoutes.flatMap((p) => {
|
|
750
|
+
const imp = p.browserImports?.adminRoutes;
|
|
751
|
+
if (!imp)
|
|
752
|
+
return [];
|
|
753
|
+
return [` { name: ${JSON.stringify(p.name)}, adminRoutes: ${imp.exportName} }`];
|
|
750
754
|
}).join(",\n");
|
|
751
755
|
lines.push(` plugins: [
|
|
752
756
|
${pluginItems},
|
|
@@ -680,7 +680,9 @@ function generateAdminConfig(config, typesRelPath) {
|
|
|
680
680
|
}
|
|
681
681
|
lines.push(`import type { ${typeImports.join(", ")} } from '${typesRelPath}';`);
|
|
682
682
|
for (const plugin of pluginsWithAdminRoutes) {
|
|
683
|
-
const imp = plugin.browserImports
|
|
683
|
+
const imp = plugin.browserImports?.adminRoutes;
|
|
684
|
+
if (!imp)
|
|
685
|
+
continue;
|
|
684
686
|
lines.push(`import { ${imp.exportName} } from '${imp.path}';`);
|
|
685
687
|
}
|
|
686
688
|
lines.push("");
|
|
@@ -713,9 +715,11 @@ ${globalItems},
|
|
|
713
715
|
}
|
|
714
716
|
}
|
|
715
717
|
if (pluginsWithAdminRoutes.length > 0) {
|
|
716
|
-
const pluginItems = pluginsWithAdminRoutes.
|
|
717
|
-
const imp = p.browserImports
|
|
718
|
-
|
|
718
|
+
const pluginItems = pluginsWithAdminRoutes.flatMap((p) => {
|
|
719
|
+
const imp = p.browserImports?.adminRoutes;
|
|
720
|
+
if (!imp)
|
|
721
|
+
return [];
|
|
722
|
+
return [` { name: ${JSON.stringify(p.name)}, adminRoutes: ${imp.exportName} }`];
|
|
719
723
|
}).join(",\n");
|
|
720
724
|
lines.push(` plugins: [
|
|
721
725
|
${pluginItems},
|
package/src/index.cjs
CHANGED
|
@@ -128,6 +128,8 @@ function getUploadFieldMapping(config) {
|
|
|
128
128
|
if (!isUploadCollection(config))
|
|
129
129
|
return null;
|
|
130
130
|
const u = config.upload;
|
|
131
|
+
if (!u)
|
|
132
|
+
return null;
|
|
131
133
|
return {
|
|
132
134
|
filename: u.filenameField ?? "filename",
|
|
133
135
|
mimeType: u.mimeTypeField ?? "mimeType",
|
|
@@ -443,6 +445,31 @@ function validateRowCount(name, label, count, minRows, maxRows, errors) {
|
|
|
443
445
|
}
|
|
444
446
|
|
|
445
447
|
// libs/core/src/lib/collections/media.collection.ts
|
|
448
|
+
var validateFocalPoint = (value) => {
|
|
449
|
+
if (value === null || value === void 0)
|
|
450
|
+
return true;
|
|
451
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
452
|
+
return "Focal point must be an object with x and y coordinates";
|
|
453
|
+
}
|
|
454
|
+
const fp = Object.fromEntries(Object.entries(value));
|
|
455
|
+
if (!("x" in fp) || !("y" in fp)) {
|
|
456
|
+
return "Focal point must have both x and y properties";
|
|
457
|
+
}
|
|
458
|
+
const { x, y } = fp;
|
|
459
|
+
if (typeof x !== "number" || !Number.isFinite(x)) {
|
|
460
|
+
return "Focal point x must be a finite number";
|
|
461
|
+
}
|
|
462
|
+
if (typeof y !== "number" || !Number.isFinite(y)) {
|
|
463
|
+
return "Focal point y must be a finite number";
|
|
464
|
+
}
|
|
465
|
+
if (x < 0 || x > 1) {
|
|
466
|
+
return `Focal point x must be between 0 and 1 (received ${x})`;
|
|
467
|
+
}
|
|
468
|
+
if (y < 0 || y > 1) {
|
|
469
|
+
return `Focal point y must be between 0 and 1 (received ${y})`;
|
|
470
|
+
}
|
|
471
|
+
return true;
|
|
472
|
+
};
|
|
446
473
|
var MediaCollection = defineCollection({
|
|
447
474
|
slug: "media",
|
|
448
475
|
labels: {
|
|
@@ -497,6 +524,14 @@ var MediaCollection = defineCollection({
|
|
|
497
524
|
json("focalPoint", {
|
|
498
525
|
label: "Focal Point",
|
|
499
526
|
description: "Focal point coordinates for image cropping",
|
|
527
|
+
validate: validateFocalPoint,
|
|
528
|
+
admin: {
|
|
529
|
+
hidden: true
|
|
530
|
+
}
|
|
531
|
+
}),
|
|
532
|
+
json("sizes", {
|
|
533
|
+
label: "Image Sizes",
|
|
534
|
+
description: "Generated image size variants",
|
|
500
535
|
admin: {
|
|
501
536
|
hidden: true
|
|
502
537
|
}
|
|
@@ -586,7 +621,7 @@ function isOwner(ownerField = "createdBy") {
|
|
|
586
621
|
function resolveMigrationMode(mode) {
|
|
587
622
|
if (mode === "push" || mode === "migrate")
|
|
588
623
|
return mode;
|
|
589
|
-
const env = process
|
|
624
|
+
const env = globalThis["process"]?.env?.["NODE_ENV"];
|
|
590
625
|
if (env === "production")
|
|
591
626
|
return "migrate";
|
|
592
627
|
return "push";
|
package/src/index.d.ts
CHANGED
package/src/index.js
CHANGED
|
@@ -47,6 +47,8 @@ function getUploadFieldMapping(config) {
|
|
|
47
47
|
if (!isUploadCollection(config))
|
|
48
48
|
return null;
|
|
49
49
|
const u = config.upload;
|
|
50
|
+
if (!u)
|
|
51
|
+
return null;
|
|
50
52
|
return {
|
|
51
53
|
filename: u.filenameField ?? "filename",
|
|
52
54
|
mimeType: u.mimeTypeField ?? "mimeType",
|
|
@@ -362,6 +364,31 @@ function validateRowCount(name, label, count, minRows, maxRows, errors) {
|
|
|
362
364
|
}
|
|
363
365
|
|
|
364
366
|
// libs/core/src/lib/collections/media.collection.ts
|
|
367
|
+
var validateFocalPoint = (value) => {
|
|
368
|
+
if (value === null || value === void 0)
|
|
369
|
+
return true;
|
|
370
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
371
|
+
return "Focal point must be an object with x and y coordinates";
|
|
372
|
+
}
|
|
373
|
+
const fp = Object.fromEntries(Object.entries(value));
|
|
374
|
+
if (!("x" in fp) || !("y" in fp)) {
|
|
375
|
+
return "Focal point must have both x and y properties";
|
|
376
|
+
}
|
|
377
|
+
const { x, y } = fp;
|
|
378
|
+
if (typeof x !== "number" || !Number.isFinite(x)) {
|
|
379
|
+
return "Focal point x must be a finite number";
|
|
380
|
+
}
|
|
381
|
+
if (typeof y !== "number" || !Number.isFinite(y)) {
|
|
382
|
+
return "Focal point y must be a finite number";
|
|
383
|
+
}
|
|
384
|
+
if (x < 0 || x > 1) {
|
|
385
|
+
return `Focal point x must be between 0 and 1 (received ${x})`;
|
|
386
|
+
}
|
|
387
|
+
if (y < 0 || y > 1) {
|
|
388
|
+
return `Focal point y must be between 0 and 1 (received ${y})`;
|
|
389
|
+
}
|
|
390
|
+
return true;
|
|
391
|
+
};
|
|
365
392
|
var MediaCollection = defineCollection({
|
|
366
393
|
slug: "media",
|
|
367
394
|
labels: {
|
|
@@ -416,6 +443,14 @@ var MediaCollection = defineCollection({
|
|
|
416
443
|
json("focalPoint", {
|
|
417
444
|
label: "Focal Point",
|
|
418
445
|
description: "Focal point coordinates for image cropping",
|
|
446
|
+
validate: validateFocalPoint,
|
|
447
|
+
admin: {
|
|
448
|
+
hidden: true
|
|
449
|
+
}
|
|
450
|
+
}),
|
|
451
|
+
json("sizes", {
|
|
452
|
+
label: "Image Sizes",
|
|
453
|
+
description: "Generated image size variants",
|
|
419
454
|
admin: {
|
|
420
455
|
hidden: true
|
|
421
456
|
}
|
|
@@ -505,7 +540,7 @@ function isOwner(ownerField = "createdBy") {
|
|
|
505
540
|
function resolveMigrationMode(mode) {
|
|
506
541
|
if (mode === "push" || mode === "migrate")
|
|
507
542
|
return mode;
|
|
508
|
-
const env = process
|
|
543
|
+
const env = globalThis["process"]?.env?.["NODE_ENV"];
|
|
509
544
|
if (env === "production")
|
|
510
545
|
return "migrate";
|
|
511
546
|
return "push";
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Defines the structure of collections (similar to Payload CMS)
|
|
4
4
|
*/
|
|
5
5
|
import type { Field } from '../fields/field.types';
|
|
6
|
+
import type { ImageSizeConfig } from '../storage';
|
|
6
7
|
export interface AccessArgs {
|
|
7
8
|
req: RequestContext;
|
|
8
9
|
id?: string | number;
|
|
@@ -137,6 +138,10 @@ export interface UploadCollectionConfig {
|
|
|
137
138
|
pathField?: string;
|
|
138
139
|
/** Field name for the public URL. @default 'url' */
|
|
139
140
|
urlField?: string;
|
|
141
|
+
/** Image sizes to generate on upload (images only) */
|
|
142
|
+
imageSizes?: ImageSizeConfig[];
|
|
143
|
+
/** Default output format for generated sizes. @default 'original' */
|
|
144
|
+
formatPreference?: 'jpeg' | 'webp' | 'avif' | 'original';
|
|
140
145
|
}
|
|
141
146
|
export interface TimestampsConfig {
|
|
142
147
|
/** Add createdAt field */
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* Built-in Media Collection for Momentum CMS
|
|
3
3
|
* Stores metadata for uploaded files
|
|
4
4
|
*/
|
|
5
|
+
import type { ValidateFunction } from '../fields/field.types';
|
|
6
|
+
/**
|
|
7
|
+
* Validates a focalPoint value: null/undefined is allowed (optional field).
|
|
8
|
+
* When present, must be a plain object with exactly `x` and `y` properties,
|
|
9
|
+
* both finite numbers in the [0, 1] range.
|
|
10
|
+
*/
|
|
11
|
+
export declare const validateFocalPoint: ValidateFunction;
|
|
5
12
|
/**
|
|
6
13
|
* Built-in Media collection for storing file upload metadata.
|
|
7
14
|
* Users can override this by defining their own 'media' collection.
|
|
@@ -24,6 +31,14 @@ export interface MediaDocument {
|
|
|
24
31
|
x: number;
|
|
25
32
|
y: number;
|
|
26
33
|
};
|
|
34
|
+
sizes?: Record<string, {
|
|
35
|
+
url: string;
|
|
36
|
+
path: string;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
mimeType: string;
|
|
40
|
+
filesize: number;
|
|
41
|
+
}>;
|
|
27
42
|
createdAt: string;
|
|
28
43
|
updatedAt: string;
|
|
29
44
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron module for Momentum CMS
|
|
3
|
+
* Defines types for recurring job schedules
|
|
4
|
+
*/
|
|
5
|
+
import type { JobPriority } from '../queue';
|
|
6
|
+
/**
|
|
7
|
+
* A recurring job schedule definition.
|
|
8
|
+
* Used by the cron plugin to periodically enqueue jobs into the queue.
|
|
9
|
+
*/
|
|
10
|
+
export interface RecurringJobDefinition {
|
|
11
|
+
/** Unique name for this recurring schedule */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Job type to enqueue (must match a registered handler in the queue plugin) */
|
|
14
|
+
type: string;
|
|
15
|
+
/** Cron expression (5-field: minute hour day-of-month month day-of-week) */
|
|
16
|
+
cron: string;
|
|
17
|
+
/** Job payload */
|
|
18
|
+
payload?: unknown;
|
|
19
|
+
/** Queue name. @default 'default' */
|
|
20
|
+
queue?: string;
|
|
21
|
+
/** Priority (0=highest, 9=lowest). @default 5 */
|
|
22
|
+
priority?: JobPriority;
|
|
23
|
+
/** Maximum retry attempts. @default 3 */
|
|
24
|
+
maxRetries?: number;
|
|
25
|
+
/** Timeout in ms. @default 30000 */
|
|
26
|
+
timeout?: number;
|
|
27
|
+
/** Whether the schedule is enabled. @default true */
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
}
|
|
@@ -20,8 +20,8 @@ export interface FieldAdminConfig {
|
|
|
20
20
|
readOnly?: boolean;
|
|
21
21
|
hidden?: boolean;
|
|
22
22
|
placeholder?: string;
|
|
23
|
-
/**
|
|
24
|
-
editor?: 'visual' | 'form';
|
|
23
|
+
/** Editor rendering mode. 'visual' enables WYSIWYG block editor; 'email-builder' enables email template builder; 'form-builder' enables form schema builder for json fields. */
|
|
24
|
+
editor?: 'visual' | 'form' | 'email-builder' | 'form-builder';
|
|
25
25
|
/** Render this group field as a collapsible accordion section */
|
|
26
26
|
collapsible?: boolean;
|
|
27
27
|
/** Whether the collapsible section starts expanded (default: false) */
|
package/src/lib/plugins.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* These live in @momentumcms/core to avoid circular dependencies.
|
|
6
6
|
* Runtime implementations (PluginRunner, etc.) live in @momentumcms/plugins/core.
|
|
7
7
|
*/
|
|
8
|
-
import type { CollectionConfig } from './collections';
|
|
8
|
+
import type { CollectionConfig, UserContext } from './collections';
|
|
9
9
|
import type { MomentumConfig } from './config';
|
|
10
10
|
/**
|
|
11
11
|
* Descriptor for Express middleware/routes that a plugin wants auto-mounted.
|
|
@@ -127,6 +127,12 @@ export interface MomentumAPI {
|
|
|
127
127
|
collection(slug: string): unknown;
|
|
128
128
|
/** Get the current config */
|
|
129
129
|
getConfig(): MomentumConfig;
|
|
130
|
+
/** Return a new API instance with merged context (immutable). */
|
|
131
|
+
setContext(ctx: {
|
|
132
|
+
user?: UserContext;
|
|
133
|
+
depth?: number;
|
|
134
|
+
showHiddenFields?: boolean;
|
|
135
|
+
}): MomentumAPI;
|
|
130
136
|
}
|
|
131
137
|
/**
|
|
132
138
|
* A Momentum CMS plugin.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue module for Momentum CMS
|
|
3
|
+
* Defines interfaces for job queue adapters
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Job status lifecycle:
|
|
7
|
+
* pending -> active -> completed
|
|
8
|
+
* -> failed (retries remain) -> pending (retry)
|
|
9
|
+
* -> dead (max retries exceeded)
|
|
10
|
+
*/
|
|
11
|
+
export type JobStatus = 'pending' | 'active' | 'completed' | 'failed' | 'dead';
|
|
12
|
+
/**
|
|
13
|
+
* Job priority levels. Lower number = higher priority.
|
|
14
|
+
* 0 is the highest priority, 9 is the lowest.
|
|
15
|
+
*/
|
|
16
|
+
export type JobPriority = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
17
|
+
/**
|
|
18
|
+
* Backoff strategy for job retries.
|
|
19
|
+
*/
|
|
20
|
+
export interface BackoffStrategy {
|
|
21
|
+
/** Backoff type */
|
|
22
|
+
type: 'exponential' | 'linear' | 'fixed';
|
|
23
|
+
/** Base delay in milliseconds */
|
|
24
|
+
delay: number;
|
|
25
|
+
/** Maximum delay in milliseconds (cap for exponential). @default 300000 (5 minutes) */
|
|
26
|
+
maxDelay?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Options when enqueuing a job.
|
|
30
|
+
*/
|
|
31
|
+
export interface EnqueueOptions {
|
|
32
|
+
/** Queue name. @default 'default' */
|
|
33
|
+
queue?: string;
|
|
34
|
+
/** Priority (0=highest, 9=lowest). @default 5 */
|
|
35
|
+
priority?: JobPriority;
|
|
36
|
+
/** Delay execution until this Date (ISO string or Date). */
|
|
37
|
+
runAt?: string | Date;
|
|
38
|
+
/** Maximum retry attempts. @default 3 */
|
|
39
|
+
maxRetries?: number;
|
|
40
|
+
/** Backoff strategy for retries. @default { type: 'exponential', delay: 1000 } */
|
|
41
|
+
backoff?: BackoffStrategy;
|
|
42
|
+
/** Maximum time in ms a job can run before being considered stalled. @default 30000 */
|
|
43
|
+
timeout?: number;
|
|
44
|
+
/** Unique key for deduplication. If a pending/active job with this key exists, the new job is skipped. */
|
|
45
|
+
uniqueKey?: string;
|
|
46
|
+
/** Arbitrary metadata attached to the job (not part of the handler payload). */
|
|
47
|
+
metadata?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A job record as stored/returned by the adapter.
|
|
51
|
+
*/
|
|
52
|
+
export interface Job<T = unknown> {
|
|
53
|
+
/** Unique job ID */
|
|
54
|
+
id: string;
|
|
55
|
+
/** Job type name (matches a registered handler) */
|
|
56
|
+
type: string;
|
|
57
|
+
/** Job payload (serialized as JSON) */
|
|
58
|
+
payload: T;
|
|
59
|
+
/** Current job status */
|
|
60
|
+
status: JobStatus;
|
|
61
|
+
/** Queue name */
|
|
62
|
+
queue: string;
|
|
63
|
+
/** Priority (0-9) */
|
|
64
|
+
priority: JobPriority;
|
|
65
|
+
/** Number of attempts made */
|
|
66
|
+
attempts: number;
|
|
67
|
+
/** Maximum retry attempts */
|
|
68
|
+
maxRetries: number;
|
|
69
|
+
/** Backoff configuration */
|
|
70
|
+
backoff: BackoffStrategy;
|
|
71
|
+
/** Timeout in milliseconds */
|
|
72
|
+
timeout: number;
|
|
73
|
+
/** Unique deduplication key */
|
|
74
|
+
uniqueKey?: string;
|
|
75
|
+
/** When the job should run (null = immediately) */
|
|
76
|
+
runAt: string | null;
|
|
77
|
+
/** When the job was last started */
|
|
78
|
+
startedAt?: string;
|
|
79
|
+
/** When the job completed or failed permanently */
|
|
80
|
+
finishedAt?: string;
|
|
81
|
+
/** Last error message (if failed/dead) */
|
|
82
|
+
lastError?: string;
|
|
83
|
+
/** Arbitrary metadata */
|
|
84
|
+
metadata?: Record<string, unknown>;
|
|
85
|
+
/** ISO timestamp of creation */
|
|
86
|
+
createdAt: string;
|
|
87
|
+
/** ISO timestamp of last update */
|
|
88
|
+
updatedAt: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Options for fetching the next batch of jobs to process.
|
|
92
|
+
*/
|
|
93
|
+
export interface FetchJobsOptions {
|
|
94
|
+
/** Queue name to fetch from. @default 'default' */
|
|
95
|
+
queue?: string;
|
|
96
|
+
/** Maximum number of jobs to fetch. @default 1 */
|
|
97
|
+
limit?: number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Options for querying jobs (admin dashboard, monitoring).
|
|
101
|
+
*/
|
|
102
|
+
export interface JobQueryOptions {
|
|
103
|
+
/** Filter by status */
|
|
104
|
+
status?: JobStatus;
|
|
105
|
+
/** Filter by queue name */
|
|
106
|
+
queue?: string;
|
|
107
|
+
/** Filter by job type */
|
|
108
|
+
type?: string;
|
|
109
|
+
/** Pagination limit. @default 50 */
|
|
110
|
+
limit?: number;
|
|
111
|
+
/** Pagination page (1-based). @default 1 */
|
|
112
|
+
page?: number;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Result of a job query.
|
|
116
|
+
*/
|
|
117
|
+
export interface JobQueryResult {
|
|
118
|
+
jobs: Job[];
|
|
119
|
+
total: number;
|
|
120
|
+
page: number;
|
|
121
|
+
limit: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Queue statistics for monitoring.
|
|
125
|
+
*/
|
|
126
|
+
export interface QueueStats {
|
|
127
|
+
/** Queue name */
|
|
128
|
+
queue: string;
|
|
129
|
+
/** Count of jobs by status */
|
|
130
|
+
counts: Record<JobStatus, number>;
|
|
131
|
+
/** Oldest pending job age in milliseconds */
|
|
132
|
+
oldestPendingAge?: number;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Queue adapter interface.
|
|
136
|
+
* Implement this interface to create custom queue backends (PostgreSQL, Redis, etc.).
|
|
137
|
+
*/
|
|
138
|
+
export interface QueueAdapter {
|
|
139
|
+
/**
|
|
140
|
+
* Initialize the queue backend (create indexes, etc.).
|
|
141
|
+
* Called once during server startup after collection tables are created.
|
|
142
|
+
*/
|
|
143
|
+
initialize(): Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* Enqueue a new job.
|
|
146
|
+
* @param type - Job type name (matches a registered handler)
|
|
147
|
+
* @param payload - Job payload data
|
|
148
|
+
* @param options - Enqueue options
|
|
149
|
+
* @returns The created job record
|
|
150
|
+
*/
|
|
151
|
+
enqueue(type: string, payload: unknown, options?: EnqueueOptions): Promise<Job>;
|
|
152
|
+
/**
|
|
153
|
+
* Fetch the next batch of jobs ready for processing.
|
|
154
|
+
* Must use atomic locking (e.g., SKIP LOCKED) to prevent double-processing.
|
|
155
|
+
* Jobs are returned in priority order (lowest number first), then by runAt/createdAt.
|
|
156
|
+
*/
|
|
157
|
+
fetchJobs(options?: FetchJobsOptions): Promise<Job[]>;
|
|
158
|
+
/**
|
|
159
|
+
* Mark a job as completed.
|
|
160
|
+
*/
|
|
161
|
+
completeJob(jobId: string): Promise<void>;
|
|
162
|
+
/**
|
|
163
|
+
* Mark a job as failed. If retries remain, schedules the next attempt.
|
|
164
|
+
* If max retries exceeded, moves to 'dead' status.
|
|
165
|
+
*/
|
|
166
|
+
failJob(jobId: string, error: string): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Query jobs for monitoring/admin UI.
|
|
169
|
+
*/
|
|
170
|
+
queryJobs(options?: JobQueryOptions): Promise<JobQueryResult>;
|
|
171
|
+
/**
|
|
172
|
+
* Get statistics for one or all queues.
|
|
173
|
+
* @param queue - Optional queue name (all queues if omitted)
|
|
174
|
+
*/
|
|
175
|
+
getStats(queue?: string): Promise<QueueStats[]>;
|
|
176
|
+
/**
|
|
177
|
+
* Get a single job by ID.
|
|
178
|
+
* @returns The job record, or null if not found
|
|
179
|
+
*/
|
|
180
|
+
getJob(jobId: string): Promise<Job | null>;
|
|
181
|
+
/**
|
|
182
|
+
* Delete a specific job by ID.
|
|
183
|
+
* @returns True if deleted
|
|
184
|
+
*/
|
|
185
|
+
deleteJob(jobId: string): Promise<boolean>;
|
|
186
|
+
/**
|
|
187
|
+
* Purge completed/dead jobs older than the given age.
|
|
188
|
+
* @param olderThanMs - Age threshold in milliseconds
|
|
189
|
+
* @param status - Status to purge. @default 'completed'
|
|
190
|
+
* @returns Number of jobs purged
|
|
191
|
+
*/
|
|
192
|
+
purgeJobs(olderThanMs: number, status?: 'completed' | 'dead'): Promise<number>;
|
|
193
|
+
/**
|
|
194
|
+
* Retry a dead job (move it back to pending with reset attempts).
|
|
195
|
+
* @returns The updated job
|
|
196
|
+
*/
|
|
197
|
+
retryJob(jobId: string): Promise<Job>;
|
|
198
|
+
/**
|
|
199
|
+
* Detect and recover stalled jobs (active jobs that exceeded their timeout).
|
|
200
|
+
* Moves them back to pending for retry, or to dead if maxRetries exceeded.
|
|
201
|
+
* @returns Number of jobs recovered
|
|
202
|
+
*/
|
|
203
|
+
recoverStalledJobs(): Promise<number>;
|
|
204
|
+
/**
|
|
205
|
+
* Graceful shutdown. Releases any held resources.
|
|
206
|
+
*/
|
|
207
|
+
shutdown(): Promise<void>;
|
|
208
|
+
}
|
|
@@ -1,7 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Storage module for Momentum CMS
|
|
3
|
-
* Defines interfaces for file storage adapters
|
|
3
|
+
* Defines interfaces for file storage adapters and image processing
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Describes one output size for image processing.
|
|
7
|
+
*/
|
|
8
|
+
export interface ImageSizeConfig {
|
|
9
|
+
/** Named key for this size (e.g. 'thumbnail', 'medium') */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Target width in pixels. Undefined = proportional from height. */
|
|
12
|
+
width?: number;
|
|
13
|
+
/** Target height in pixels. Undefined = proportional from width. */
|
|
14
|
+
height?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Resizing strategy.
|
|
17
|
+
* - 'contain': shrink to fit, no cropping
|
|
18
|
+
* - 'cover': resize + crop to fill exact dimensions (uses focalPoint)
|
|
19
|
+
* - 'fill': stretch to exact dimensions
|
|
20
|
+
* - 'width': resize to width, height proportional
|
|
21
|
+
* - 'height': resize to height, width proportional
|
|
22
|
+
* @default 'cover'
|
|
23
|
+
*/
|
|
24
|
+
fit?: 'contain' | 'cover' | 'fill' | 'width' | 'height';
|
|
25
|
+
/** Output format. When undefined, uses source format or global formatPreference. */
|
|
26
|
+
format?: 'jpeg' | 'webp' | 'avif' | 'png';
|
|
27
|
+
/** JPEG/WebP/AVIF quality (1-100). @default 80 */
|
|
28
|
+
quality?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Image dimensions in pixels.
|
|
32
|
+
*/
|
|
33
|
+
export interface ImageDimensions {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pluggable image processor interface.
|
|
39
|
+
* Implement this to provide custom image processing backends.
|
|
40
|
+
*/
|
|
41
|
+
export interface ImageProcessor {
|
|
42
|
+
/** Detect image dimensions without full decode when possible. */
|
|
43
|
+
getDimensions(buffer: Uint8Array, mimeType: string): Promise<ImageDimensions>;
|
|
44
|
+
/** Process one size variant. Returns the processed buffer and its dimensions. */
|
|
45
|
+
processVariant(buffer: Uint8Array, mimeType: string, size: ImageSizeConfig, focalPoint?: {
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
buffer: Uint8Array;
|
|
50
|
+
width: number;
|
|
51
|
+
height: number;
|
|
52
|
+
mimeType: string;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
5
55
|
/**
|
|
6
56
|
* Represents an uploaded file before storage.
|
|
7
57
|
*/
|
|
@@ -12,8 +62,8 @@ export interface UploadedFile {
|
|
|
12
62
|
mimeType: string;
|
|
13
63
|
/** File size in bytes */
|
|
14
64
|
size: number;
|
|
15
|
-
/** File content
|
|
16
|
-
buffer:
|
|
65
|
+
/** File content (Uint8Array for universal compat; Node Buffer extends Uint8Array) */
|
|
66
|
+
buffer: Uint8Array;
|
|
17
67
|
}
|
|
18
68
|
/**
|
|
19
69
|
* Represents a file after it has been stored.
|
|
@@ -82,5 +132,5 @@ export interface StorageAdapter {
|
|
|
82
132
|
* @param path - The storage path/key
|
|
83
133
|
* @returns The file as a Buffer, or null if not found
|
|
84
134
|
*/
|
|
85
|
-
read?(path: string): Promise<
|
|
135
|
+
read?(path: string): Promise<Uint8Array | null>;
|
|
86
136
|
}
|