@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momentumcms/core",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Core collection config, fields, hooks, and access control for Momentum CMS",
5
5
  "license": "MIT",
6
6
  "author": "Momentum CMS Contributors",
@@ -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.adminRoutes;
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.map((p) => {
748
- const imp = p.browserImports.adminRoutes;
749
- return ` { name: ${JSON.stringify(p.name)}, adminRoutes: ${imp.exportName} }`;
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.adminRoutes;
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.map((p) => {
717
- const imp = p.browserImports.adminRoutes;
718
- return ` { name: ${JSON.stringify(p.name)}, adminRoutes: ${imp.exportName} }`;
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.env["NODE_ENV"];
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
@@ -13,3 +13,5 @@ export * from './lib/storage';
13
13
  export * from './lib/seeding';
14
14
  export * from './lib/versions';
15
15
  export * from './lib/migrations';
16
+ export * from './lib/queue';
17
+ export * from './lib/cron';
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.env["NODE_ENV"];
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
- /** For blocks fields: editor rendering mode. 'visual' enables the WYSIWYG block editor. */
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) */
@@ -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 as Buffer */
16
- buffer: 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<Buffer | null>;
135
+ read?(path: string): Promise<Uint8Array | null>;
86
136
  }