@better-media/framework 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 abenezeratnafu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
20
+ ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @better-media/framework
2
+
3
+ The core runtime orchestration engine for Better Media pipelines.
4
+
5
+ ## Overview
6
+
7
+ Better Media is a modular media framework that handles the entire lifecycle of a file ingest—from intake and validation to transformation and multi-environment storage.
8
+
9
+ This package provides the `createBetterMedia` factory and the core execution logic to coordinate:
10
+
11
+ 1. **Ingest**: Handle binary, stream, or file-path inputs.
12
+ 2. **Validation**: Run plugins to check file size, MIME type, or integrity.
13
+ 3. **Persistence**: Save to SQL, NoSQL, or custom database adapters.
14
+ 4. **Storage**: Move files to local disk, S3, or in-memory targets.
15
+ 5. **Processing**: Run background or synchronous jobs (resizing, transpoding, etc).
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pnpm add @better-media/framework
21
+ ```
22
+
23
+ ## Basic Usage
24
+
25
+ ```ts
26
+ import { createBetterMedia } from "@better-media/framework";
27
+ import { S3StorageAdapter } from "@better-media/adapter-storage-s3";
28
+ import { KyselyDatabaseAdapter } from "@better-media/adapter-db";
29
+ import { validationPlugin } from "@better-media/plugin-validation";
30
+
31
+ // 1. Configure the runtime
32
+ const media = createBetterMedia({
33
+ storage: new S3StorageAdapter(s3Config),
34
+ database: new KyselyDatabaseAdapter(dbConfig),
35
+ plugins: [validationPlugin({ maxSize: "10mb", allowedMimeTypes: ["image/jpeg", "image/png"] })],
36
+ });
37
+
38
+ // 2. Process an upload
39
+ const result = await media.upload({
40
+ file: myFileBuffer,
41
+ fileName: "profile-pic.jpg",
42
+ });
43
+
44
+ console.log("Uploaded file key:", result.fileKey);
45
+ ```
46
+
47
+ ## Documentation
48
+
49
+ Full documentation is available at [better-media.dev](https://better-media.dev).
package/bin/media.mjs ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import "@better-media/cli";
@@ -0,0 +1,225 @@
1
+ import { HookName, PipelineContext, PluginApi, ValidationResult, PluginManifest, JobAdapter, MediaPlugin, HookContext, PipelinePlugin, MediaRuntime, PgPoolLike, DatabaseAdapter, StorageAdapter, PresignedUploadOptions, PresignedUploadResult, GetUrlOptions } from '@better-media/core';
2
+ export { BmSchema, DatabaseHookContext, DbHooks, FieldDefinition, FieldType, GetAdapterOptions, GetUrlOptions, HOOK_NAMES, HookContext, IndexDefinition, MigrationOperation, MigrationOptions, MigrationPlanner, ModelDefinition, PgClientLike, PgPoolLike, PresignedUploadMethod, PresignedUploadOptions, PresignedUploadResult, QueryResultLike, Queryable, SqlDialect, TableMetadata, applyOperationsToMetadata, compileMigrationOperationsSql, deserializeData, getAdapter, getColumnType, getMigrations, isPgPoolLike, runHooks, runMigrations, schema, serializeData, toCamelCase, toDbFieldName } from '@better-media/core';
3
+ import { Readable } from 'node:stream';
4
+
5
+ interface BetterMediaSettings {
6
+ /** Log level for the framework */
7
+ logLevel?: "debug" | "info" | "warn" | "error";
8
+ }
9
+
10
+ interface FileHandlingConfig {
11
+ /** When set, files larger than this use streaming to temp file instead of loading into memory. */
12
+ maxBufferBytes?: number;
13
+ }
14
+
15
+ /** Single tapped handler entry */
16
+ interface TapInfo {
17
+ name: string;
18
+ fn: (ctx: PipelineContext, api: PluginApi) => Promise<void | ValidationResult>;
19
+ mode: "sync" | "background";
20
+ manifest: PluginManifest;
21
+ stage?: number;
22
+ }
23
+ /** Registry: hook name -> ordered list of handlers */
24
+ type HookRegistry = Map<HookName, TapInfo[]>;
25
+
26
+ /**
27
+ * Policy gate for explicitly authorizing trusted plugins.
28
+ * Prevents unauthorized plugins from claiming "trusted" status.
29
+ */
30
+ interface TrustedPluginPolicy {
31
+ isAuthorized(pluginId: string, namespace: string): boolean;
32
+ }
33
+ /**
34
+ * Build hook registry from plugins via apply pattern.
35
+ */
36
+ declare function buildPluginRegistry(plugins: PipelinePlugin[], policy?: TrustedPluginPolicy): {
37
+ registry: HookRegistry;
38
+ runtime: MediaRuntime;
39
+ };
40
+
41
+ /**
42
+ * Validates that a plugin has required fields (name, apply or execute)
43
+ * and a valid runtimeManifest.
44
+ * @throws Error if plugin is invalid
45
+ */
46
+ declare function validatePlugin(plugin: PipelinePlugin): void;
47
+ /**
48
+ * PluginRegistry: register plugins, maintain hook maps, execute hooks.
49
+ * Allows dynamic registration and type-safe hook execution.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const registry = new PluginRegistry(jobAdapter);
54
+ * registry.register(validationPlugin());
55
+ * registry.register(thumbnailPlugin({ mode: "background" }));
56
+ *
57
+ * await registry.executeHook("validation:run", context);
58
+ * ```
59
+ */
60
+ declare class PluginRegistry {
61
+ private readonly plugins;
62
+ private readonly registry;
63
+ private readonly engine;
64
+ constructor(jobAdapter: JobAdapter, initialPlugins?: MediaPlugin[]);
65
+ /** Register a plugin and rebuild the hook map */
66
+ register(plugin: MediaPlugin): void;
67
+ /** Get all registered plugins (shallow copy) */
68
+ getPlugins(): MediaPlugin[];
69
+ /**
70
+ * Execute all handlers for a hook. Runs sync handlers in series.
71
+ * Enqueues background handlers via JobAdapter.
72
+ * @returns ValidationResult if validation phase aborts (valid: false)
73
+ */
74
+ executeHook(hookName: HookName, context: HookContext): Promise<void | ValidationResult>;
75
+ /** Access the internal hook registry (for framework wiring) */
76
+ getRegistry(): HookRegistry;
77
+ }
78
+ /** Check if any handler in the registry is background mode */
79
+ declare function hasBackgroundHandlers(registry: HookRegistry): boolean;
80
+
81
+ declare function postgresDatabase(pool: PgPoolLike): DatabaseAdapter;
82
+ declare function toDatabaseAdapter(database: DatabaseAdapter | PgPoolLike): DatabaseAdapter;
83
+
84
+ /** Configuration for the Better Media framework */
85
+ interface BetterMediaConfig {
86
+ /** Storage adapter for file bytes (S3, GCS, local, etc.) */
87
+ storage: StorageAdapter;
88
+ /** Database adapter or a Postgres Pool (built-in) for media metadata/records */
89
+ database: DatabaseAdapter | PgPoolLike;
90
+ /** Plugins to run in order during the lifecycle */
91
+ plugins: PipelinePlugin[];
92
+ /** Optional job adapter for background execution (default: in-memory) */
93
+ jobs?: JobAdapter;
94
+ /** Optional settings */
95
+ settings?: BetterMediaSettings;
96
+ /**
97
+ * Optional file handling. When maxBufferBytes is set, files larger than that
98
+ * are streamed to a temp file instead of loaded into memory.
99
+ */
100
+ fileHandling?: FileHandlingConfig;
101
+ /** Optional policy gate for authorizing trusted plugins */
102
+ trustedPolicy?: TrustedPluginPolicy;
103
+ }
104
+
105
+ /** Background job payload (serializable) */
106
+ interface BackgroundJobPayload {
107
+ recordId: string;
108
+ metadata: Record<string, unknown>;
109
+ file: PipelineContext["file"];
110
+ storageLocation: PipelineContext["storageLocation"];
111
+ processing: PipelineContext["processing"];
112
+ hookName: HookName;
113
+ pluginName: string;
114
+ manifest: PluginManifest;
115
+ }
116
+
117
+ type MediaFileInput = {
118
+ buffer: Buffer;
119
+ } | {
120
+ stream: Readable;
121
+ } | {
122
+ path: string;
123
+ } | {
124
+ url: string;
125
+ mode?: "import" | "reference";
126
+ };
127
+ type MediaMetadata = {
128
+ filename?: string;
129
+ mimeType?: string;
130
+ size?: number;
131
+ context?: Record<string, unknown>;
132
+ [key: string]: unknown;
133
+ };
134
+ type IngestInput = {
135
+ file: MediaFileInput;
136
+ metadata?: MediaMetadata;
137
+ key?: string;
138
+ /**
139
+ * When `file` is a filesystem path, whether to delete that file after ingest completes.
140
+ * Defaults to `true` (typical for temp uploads, e.g. Multer). Set to `false` when the path
141
+ * points at a permanent user file that must be kept on disk.
142
+ */
143
+ deleteAfterUpload?: boolean;
144
+ };
145
+ type MediaResult = {
146
+ /** The unique database record identifier (UUID). */
147
+ id: string;
148
+ /** The storage key (filename or path). */
149
+ key: string;
150
+ url?: string;
151
+ metadata?: MediaMetadata;
152
+ status: "stored" | "processed";
153
+ };
154
+ /** Metadata for uploads (e.g. contentType). Accepted at upload time. */
155
+ type Metadata = MediaMetadata;
156
+ /** File record in database (one per file). */
157
+ type FileRecord = MediaMetadata;
158
+ interface BetterMediaRuntime {
159
+ upload: {
160
+ ingest(input: IngestInput): Promise<MediaResult>;
161
+ fromBuffer(buffer: Buffer, input?: Omit<IngestInput, "file">): Promise<MediaResult>;
162
+ fromStream(stream: Readable, input?: Omit<IngestInput, "file">): Promise<MediaResult>;
163
+ fromPath(path: string, input?: Omit<IngestInput, "file">): Promise<MediaResult>;
164
+ fromUrl(url: string, input?: Omit<IngestInput, "file"> & {
165
+ mode?: "import" | "reference";
166
+ }): Promise<MediaResult>;
167
+ /**
168
+ * Create a presigned upload for direct-to-storage upload.
169
+ * Supports both PUT (binary body) and POST (multipart form) with strict server-side validation.
170
+ * After the client uploads, call `complete()` to run the processing pipeline.
171
+ */
172
+ requestPresignedUpload(key: string, options: PresignedUploadOptions): Promise<PresignedUploadResult>;
173
+ /** Called by the client *after* successfully uploading to the presigned URL */
174
+ complete(key: string, metadata?: MediaMetadata): Promise<MediaResult>;
175
+ };
176
+ /** File operations */
177
+ files: {
178
+ get(fileKey: string): Promise<FileRecord | null>;
179
+ delete(fileKey: string): Promise<void>;
180
+ getUrl(fileKey: string, options?: GetUrlOptions): Promise<string>;
181
+ reprocess(fileKey: string, metadata?: Metadata): Promise<void>;
182
+ };
183
+ /** Execute background job (call from worker). */
184
+ runBackgroundJob(payload: BackgroundJobPayload): Promise<void>;
185
+ }
186
+
187
+ /** Error thrown when validation phase aborts the pipeline */
188
+ declare class ValidationError extends Error {
189
+ readonly recordId: string;
190
+ readonly fileKey: string;
191
+ readonly result: ValidationResult;
192
+ constructor(recordId: string, fileKey: string, result: ValidationResult);
193
+ }
194
+
195
+ /**
196
+ * Create and initialize the Better Media runtime.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * const media = createBetterMedia({
201
+ * storage: new S3StorageAdapter({
202
+ * bucket: "my-bucket",
203
+ * region: "us-east-1",
204
+ * accessKeyId: "...",
205
+ * secretAccessKey: "..."
206
+ * }),
207
+ * database: postgresAdapter(...),
208
+ * jobs: redisJobAdapter(),
209
+ * plugins: [
210
+ * validationPlugin(),
211
+ * virusScanPlugin(),
212
+ * thumbnailPlugin({ mode: "background" }),
213
+ * videoProcessingPlugin({ mode: "background" })
214
+ * ]
215
+ * });
216
+ *
217
+ * const result = await media.upload.ingest({
218
+ * file: { buffer },
219
+ * metadata: { filename: "photo.jpg", mimeType: "image/jpeg" },
220
+ * });
221
+ * ```
222
+ */
223
+ declare function createBetterMedia(config: BetterMediaConfig): BetterMediaRuntime;
224
+
225
+ export { type BackgroundJobPayload, type BetterMediaRuntime, type FileHandlingConfig, type FileRecord, type IngestInput, type MediaFileInput, type MediaMetadata, type MediaResult, type Metadata, PluginRegistry, ValidationError, buildPluginRegistry, createBetterMedia, hasBackgroundHandlers, postgresDatabase, toDatabaseAdapter, validatePlugin };
@@ -0,0 +1,225 @@
1
+ import { HookName, PipelineContext, PluginApi, ValidationResult, PluginManifest, JobAdapter, MediaPlugin, HookContext, PipelinePlugin, MediaRuntime, PgPoolLike, DatabaseAdapter, StorageAdapter, PresignedUploadOptions, PresignedUploadResult, GetUrlOptions } from '@better-media/core';
2
+ export { BmSchema, DatabaseHookContext, DbHooks, FieldDefinition, FieldType, GetAdapterOptions, GetUrlOptions, HOOK_NAMES, HookContext, IndexDefinition, MigrationOperation, MigrationOptions, MigrationPlanner, ModelDefinition, PgClientLike, PgPoolLike, PresignedUploadMethod, PresignedUploadOptions, PresignedUploadResult, QueryResultLike, Queryable, SqlDialect, TableMetadata, applyOperationsToMetadata, compileMigrationOperationsSql, deserializeData, getAdapter, getColumnType, getMigrations, isPgPoolLike, runHooks, runMigrations, schema, serializeData, toCamelCase, toDbFieldName } from '@better-media/core';
3
+ import { Readable } from 'node:stream';
4
+
5
+ interface BetterMediaSettings {
6
+ /** Log level for the framework */
7
+ logLevel?: "debug" | "info" | "warn" | "error";
8
+ }
9
+
10
+ interface FileHandlingConfig {
11
+ /** When set, files larger than this use streaming to temp file instead of loading into memory. */
12
+ maxBufferBytes?: number;
13
+ }
14
+
15
+ /** Single tapped handler entry */
16
+ interface TapInfo {
17
+ name: string;
18
+ fn: (ctx: PipelineContext, api: PluginApi) => Promise<void | ValidationResult>;
19
+ mode: "sync" | "background";
20
+ manifest: PluginManifest;
21
+ stage?: number;
22
+ }
23
+ /** Registry: hook name -> ordered list of handlers */
24
+ type HookRegistry = Map<HookName, TapInfo[]>;
25
+
26
+ /**
27
+ * Policy gate for explicitly authorizing trusted plugins.
28
+ * Prevents unauthorized plugins from claiming "trusted" status.
29
+ */
30
+ interface TrustedPluginPolicy {
31
+ isAuthorized(pluginId: string, namespace: string): boolean;
32
+ }
33
+ /**
34
+ * Build hook registry from plugins via apply pattern.
35
+ */
36
+ declare function buildPluginRegistry(plugins: PipelinePlugin[], policy?: TrustedPluginPolicy): {
37
+ registry: HookRegistry;
38
+ runtime: MediaRuntime;
39
+ };
40
+
41
+ /**
42
+ * Validates that a plugin has required fields (name, apply or execute)
43
+ * and a valid runtimeManifest.
44
+ * @throws Error if plugin is invalid
45
+ */
46
+ declare function validatePlugin(plugin: PipelinePlugin): void;
47
+ /**
48
+ * PluginRegistry: register plugins, maintain hook maps, execute hooks.
49
+ * Allows dynamic registration and type-safe hook execution.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const registry = new PluginRegistry(jobAdapter);
54
+ * registry.register(validationPlugin());
55
+ * registry.register(thumbnailPlugin({ mode: "background" }));
56
+ *
57
+ * await registry.executeHook("validation:run", context);
58
+ * ```
59
+ */
60
+ declare class PluginRegistry {
61
+ private readonly plugins;
62
+ private readonly registry;
63
+ private readonly engine;
64
+ constructor(jobAdapter: JobAdapter, initialPlugins?: MediaPlugin[]);
65
+ /** Register a plugin and rebuild the hook map */
66
+ register(plugin: MediaPlugin): void;
67
+ /** Get all registered plugins (shallow copy) */
68
+ getPlugins(): MediaPlugin[];
69
+ /**
70
+ * Execute all handlers for a hook. Runs sync handlers in series.
71
+ * Enqueues background handlers via JobAdapter.
72
+ * @returns ValidationResult if validation phase aborts (valid: false)
73
+ */
74
+ executeHook(hookName: HookName, context: HookContext): Promise<void | ValidationResult>;
75
+ /** Access the internal hook registry (for framework wiring) */
76
+ getRegistry(): HookRegistry;
77
+ }
78
+ /** Check if any handler in the registry is background mode */
79
+ declare function hasBackgroundHandlers(registry: HookRegistry): boolean;
80
+
81
+ declare function postgresDatabase(pool: PgPoolLike): DatabaseAdapter;
82
+ declare function toDatabaseAdapter(database: DatabaseAdapter | PgPoolLike): DatabaseAdapter;
83
+
84
+ /** Configuration for the Better Media framework */
85
+ interface BetterMediaConfig {
86
+ /** Storage adapter for file bytes (S3, GCS, local, etc.) */
87
+ storage: StorageAdapter;
88
+ /** Database adapter or a Postgres Pool (built-in) for media metadata/records */
89
+ database: DatabaseAdapter | PgPoolLike;
90
+ /** Plugins to run in order during the lifecycle */
91
+ plugins: PipelinePlugin[];
92
+ /** Optional job adapter for background execution (default: in-memory) */
93
+ jobs?: JobAdapter;
94
+ /** Optional settings */
95
+ settings?: BetterMediaSettings;
96
+ /**
97
+ * Optional file handling. When maxBufferBytes is set, files larger than that
98
+ * are streamed to a temp file instead of loaded into memory.
99
+ */
100
+ fileHandling?: FileHandlingConfig;
101
+ /** Optional policy gate for authorizing trusted plugins */
102
+ trustedPolicy?: TrustedPluginPolicy;
103
+ }
104
+
105
+ /** Background job payload (serializable) */
106
+ interface BackgroundJobPayload {
107
+ recordId: string;
108
+ metadata: Record<string, unknown>;
109
+ file: PipelineContext["file"];
110
+ storageLocation: PipelineContext["storageLocation"];
111
+ processing: PipelineContext["processing"];
112
+ hookName: HookName;
113
+ pluginName: string;
114
+ manifest: PluginManifest;
115
+ }
116
+
117
+ type MediaFileInput = {
118
+ buffer: Buffer;
119
+ } | {
120
+ stream: Readable;
121
+ } | {
122
+ path: string;
123
+ } | {
124
+ url: string;
125
+ mode?: "import" | "reference";
126
+ };
127
+ type MediaMetadata = {
128
+ filename?: string;
129
+ mimeType?: string;
130
+ size?: number;
131
+ context?: Record<string, unknown>;
132
+ [key: string]: unknown;
133
+ };
134
+ type IngestInput = {
135
+ file: MediaFileInput;
136
+ metadata?: MediaMetadata;
137
+ key?: string;
138
+ /**
139
+ * When `file` is a filesystem path, whether to delete that file after ingest completes.
140
+ * Defaults to `true` (typical for temp uploads, e.g. Multer). Set to `false` when the path
141
+ * points at a permanent user file that must be kept on disk.
142
+ */
143
+ deleteAfterUpload?: boolean;
144
+ };
145
+ type MediaResult = {
146
+ /** The unique database record identifier (UUID). */
147
+ id: string;
148
+ /** The storage key (filename or path). */
149
+ key: string;
150
+ url?: string;
151
+ metadata?: MediaMetadata;
152
+ status: "stored" | "processed";
153
+ };
154
+ /** Metadata for uploads (e.g. contentType). Accepted at upload time. */
155
+ type Metadata = MediaMetadata;
156
+ /** File record in database (one per file). */
157
+ type FileRecord = MediaMetadata;
158
+ interface BetterMediaRuntime {
159
+ upload: {
160
+ ingest(input: IngestInput): Promise<MediaResult>;
161
+ fromBuffer(buffer: Buffer, input?: Omit<IngestInput, "file">): Promise<MediaResult>;
162
+ fromStream(stream: Readable, input?: Omit<IngestInput, "file">): Promise<MediaResult>;
163
+ fromPath(path: string, input?: Omit<IngestInput, "file">): Promise<MediaResult>;
164
+ fromUrl(url: string, input?: Omit<IngestInput, "file"> & {
165
+ mode?: "import" | "reference";
166
+ }): Promise<MediaResult>;
167
+ /**
168
+ * Create a presigned upload for direct-to-storage upload.
169
+ * Supports both PUT (binary body) and POST (multipart form) with strict server-side validation.
170
+ * After the client uploads, call `complete()` to run the processing pipeline.
171
+ */
172
+ requestPresignedUpload(key: string, options: PresignedUploadOptions): Promise<PresignedUploadResult>;
173
+ /** Called by the client *after* successfully uploading to the presigned URL */
174
+ complete(key: string, metadata?: MediaMetadata): Promise<MediaResult>;
175
+ };
176
+ /** File operations */
177
+ files: {
178
+ get(fileKey: string): Promise<FileRecord | null>;
179
+ delete(fileKey: string): Promise<void>;
180
+ getUrl(fileKey: string, options?: GetUrlOptions): Promise<string>;
181
+ reprocess(fileKey: string, metadata?: Metadata): Promise<void>;
182
+ };
183
+ /** Execute background job (call from worker). */
184
+ runBackgroundJob(payload: BackgroundJobPayload): Promise<void>;
185
+ }
186
+
187
+ /** Error thrown when validation phase aborts the pipeline */
188
+ declare class ValidationError extends Error {
189
+ readonly recordId: string;
190
+ readonly fileKey: string;
191
+ readonly result: ValidationResult;
192
+ constructor(recordId: string, fileKey: string, result: ValidationResult);
193
+ }
194
+
195
+ /**
196
+ * Create and initialize the Better Media runtime.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * const media = createBetterMedia({
201
+ * storage: new S3StorageAdapter({
202
+ * bucket: "my-bucket",
203
+ * region: "us-east-1",
204
+ * accessKeyId: "...",
205
+ * secretAccessKey: "..."
206
+ * }),
207
+ * database: postgresAdapter(...),
208
+ * jobs: redisJobAdapter(),
209
+ * plugins: [
210
+ * validationPlugin(),
211
+ * virusScanPlugin(),
212
+ * thumbnailPlugin({ mode: "background" }),
213
+ * videoProcessingPlugin({ mode: "background" })
214
+ * ]
215
+ * });
216
+ *
217
+ * const result = await media.upload.ingest({
218
+ * file: { buffer },
219
+ * metadata: { filename: "photo.jpg", mimeType: "image/jpeg" },
220
+ * });
221
+ * ```
222
+ */
223
+ declare function createBetterMedia(config: BetterMediaConfig): BetterMediaRuntime;
224
+
225
+ export { type BackgroundJobPayload, type BetterMediaRuntime, type FileHandlingConfig, type FileRecord, type IngestInput, type MediaFileInput, type MediaMetadata, type MediaResult, type Metadata, PluginRegistry, ValidationError, buildPluginRegistry, createBetterMedia, hasBackgroundHandlers, postgresDatabase, toDatabaseAdapter, validatePlugin };