@momentumcms/core 0.4.1 → 0.5.1
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 +3 -1
- package/src/index.d.ts +2 -0
- package/src/index.js +3 -1
- 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 +3 -3
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",
|
|
@@ -586,7 +588,7 @@ function isOwner(ownerField = "createdBy") {
|
|
|
586
588
|
function resolveMigrationMode(mode) {
|
|
587
589
|
if (mode === "push" || mode === "migrate")
|
|
588
590
|
return mode;
|
|
589
|
-
const env = process
|
|
591
|
+
const env = globalThis["process"]?.env?.["NODE_ENV"];
|
|
590
592
|
if (env === "production")
|
|
591
593
|
return "migrate";
|
|
592
594
|
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",
|
|
@@ -505,7 +507,7 @@ function isOwner(ownerField = "createdBy") {
|
|
|
505
507
|
function resolveMigrationMode(mode) {
|
|
506
508
|
if (mode === "push" || mode === "migrate")
|
|
507
509
|
return mode;
|
|
508
|
-
const env = process
|
|
510
|
+
const env = globalThis["process"]?.env?.["NODE_ENV"];
|
|
509
511
|
if (env === "production")
|
|
510
512
|
return "migrate";
|
|
511
513
|
return "push";
|
|
@@ -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
|
+
}
|
|
@@ -12,8 +12,8 @@ export interface UploadedFile {
|
|
|
12
12
|
mimeType: string;
|
|
13
13
|
/** File size in bytes */
|
|
14
14
|
size: number;
|
|
15
|
-
/** File content
|
|
16
|
-
buffer:
|
|
15
|
+
/** File content (Uint8Array for universal compat; Node Buffer extends Uint8Array) */
|
|
16
|
+
buffer: Uint8Array;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Represents a file after it has been stored.
|
|
@@ -82,5 +82,5 @@ export interface StorageAdapter {
|
|
|
82
82
|
* @param path - The storage path/key
|
|
83
83
|
* @returns The file as a Buffer, or null if not found
|
|
84
84
|
*/
|
|
85
|
-
read?(path: string): Promise<
|
|
85
|
+
read?(path: string): Promise<Uint8Array | null>;
|
|
86
86
|
}
|