@better-media/framework 0.1.0 → 0.2.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/index.mjs CHANGED
@@ -1,29 +1,14 @@
1
- import { isPgPoolLike, TrustedMetadataSchema, HOOK_NAMES, schema, serializeData, toDbFieldName, deserializeData, toCamelCase, getColumnType, markFileContentVerified, resolveHookMode } from '@better-media/core';
2
- export { HOOK_NAMES, MigrationPlanner, applyOperationsToMetadata, compileMigrationOperationsSql, deserializeData, getAdapter, getColumnType, getMigrations, isPgPoolLike, runHooks, runMigrations, schema, serializeData, toCamelCase, toDbFieldName } from '@better-media/core';
3
1
  import fs2 from 'fs/promises';
4
2
  import { randomUUID } from 'crypto';
5
3
  import { memoryJobAdapter } from '@better-media/adapter-jobs';
6
- import path from 'path';
4
+ import { isPgPoolLike, HOOK_NAMES, schema, serializeData, toDbFieldName, deserializeData, toCamelCase, getColumnType, TrustedMetadataSchema, markFileContentVerified, resolveHookMode } from '@better-media/core';
5
+ export { HOOK_NAMES, MigrationPlanner, applyOperationsToMetadata, compileMigrationOperationsSql, deserializeData, getAdapter, getColumnType, getMigrations, isPgPoolLike, runHooks, runMigrations, schema, serializeData, toCamelCase, toDbFieldName } from '@better-media/core';
6
+ import path2 from 'path';
7
7
  import { createWriteStream } from 'fs';
8
8
  import os from 'os';
9
9
  import { Readable } from 'stream';
10
10
 
11
- var __defProp = Object.defineProperty;
12
- var __getOwnPropNames = Object.getOwnPropertyNames;
13
- var __esm = (fn, res) => function __init() {
14
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
- };
16
- var __export = (target, all) => {
17
- for (var name in all)
18
- __defProp(target, name, { get: all[name], enumerable: true });
19
- };
20
-
21
- // src/core/lifecycle-engine.ts
22
- var lifecycle_engine_exports = {};
23
- __export(lifecycle_engine_exports, {
24
- LifecycleEngine: () => LifecycleEngine,
25
- createSecureContext: () => createSecureContext
26
- });
11
+ // src/index.ts
27
12
  function createSecureContext(context, pluginName, namespace, trustLevel, capabilities) {
28
13
  const api = {
29
14
  emitMetadata(patch) {
@@ -120,52 +105,48 @@ function createSecureContext(context, pluginName, namespace, trustLevel, capabil
120
105
  });
121
106
  return { proxy, api };
122
107
  }
123
- var JOB_QUEUE_NAME, LifecycleEngine;
124
- var init_lifecycle_engine = __esm({
125
- "src/core/lifecycle-engine.ts"() {
126
- JOB_QUEUE_NAME = "better-media:background";
127
- LifecycleEngine = class {
128
- constructor(registry, jobAdapter) {
129
- this.registry = registry;
130
- this.jobAdapter = jobAdapter;
131
- }
132
- async trigger(hookName, context) {
133
- const handlers = this.registry.get(hookName) ?? [];
134
- const syncHandlers = handlers.filter((h) => h.mode === "sync");
135
- const backgroundHandlers = handlers.filter((h) => h.mode === "background");
136
- for (const { name, fn, manifest } of syncHandlers) {
137
- const { proxy, api } = createSecureContext(
138
- context,
139
- name,
140
- manifest.namespace,
141
- manifest.trustLevel,
142
- manifest.capabilities
143
- );
144
- const result = await fn(proxy, api);
145
- if (result !== void 0 && typeof result === "object" && "valid" in result) {
146
- if (result.valid === false) return result;
147
- }
148
- }
149
- for (const { name, manifest } of backgroundHandlers) {
150
- const payload = {
151
- recordId: context.recordId,
152
- metadata: JSON.parse(JSON.stringify(context.metadata)),
153
- file: JSON.parse(JSON.stringify(context.file)),
154
- storageLocation: JSON.parse(JSON.stringify(context.storageLocation)),
155
- processing: JSON.parse(JSON.stringify(context.processing)),
156
- hookName,
157
- pluginName: name,
158
- manifest: JSON.parse(JSON.stringify(manifest))
159
- };
160
- await this.jobAdapter.enqueue(JOB_QUEUE_NAME, payload);
161
- }
108
+
109
+ // src/core/lifecycle-engine.ts
110
+ var JOB_QUEUE_NAME = "better-media:background";
111
+ var LifecycleEngine = class {
112
+ constructor(registry, jobAdapter) {
113
+ this.registry = registry;
114
+ this.jobAdapter = jobAdapter;
115
+ }
116
+ async trigger(hookName, context) {
117
+ const handlers = this.registry.get(hookName) ?? [];
118
+ const syncHandlers = handlers.filter((h) => h.mode === "sync");
119
+ const backgroundHandlers = handlers.filter((h) => h.mode === "background");
120
+ for (const { name, fn, manifest } of syncHandlers) {
121
+ const { proxy, api } = createSecureContext(
122
+ context,
123
+ name,
124
+ manifest.namespace,
125
+ manifest.trustLevel,
126
+ manifest.capabilities
127
+ );
128
+ const result = await fn(proxy, api);
129
+ if (result !== void 0 && typeof result === "object" && "valid" in result) {
130
+ if (result.valid === false) return result;
162
131
  }
163
- };
132
+ }
133
+ for (const { name, manifest } of backgroundHandlers) {
134
+ const payload = {
135
+ recordId: context.recordId,
136
+ metadata: JSON.parse(JSON.stringify(context.metadata)),
137
+ file: JSON.parse(JSON.stringify(context.file)),
138
+ storageLocation: JSON.parse(JSON.stringify(context.storageLocation)),
139
+ processing: JSON.parse(JSON.stringify(context.processing)),
140
+ hookName,
141
+ pluginName: name,
142
+ manifest: JSON.parse(JSON.stringify(manifest))
143
+ };
144
+ await this.jobAdapter.enqueue(JOB_QUEUE_NAME, payload);
145
+ }
164
146
  }
165
- });
147
+ };
166
148
 
167
149
  // src/plugins/plugin-registry.ts
168
- init_lifecycle_engine();
169
150
  function createEmptyRegistry() {
170
151
  const reg = /* @__PURE__ */ new Map();
171
152
  for (const name of HOOK_NAMES) {
@@ -329,9 +310,6 @@ function hasBackgroundHandlers(registry) {
329
310
  }
330
311
  return false;
331
312
  }
332
-
333
- // src/index.ts
334
- init_lifecycle_engine();
335
313
  async function loadTrustedFromDb(database, recordId) {
336
314
  const record = await database.findOne({
337
315
  model: "media",
@@ -400,8 +378,8 @@ async function saveTrustedToDb(database, recordId, fileKey, trusted, initialArgs
400
378
  }
401
379
  }
402
380
  async function streamToTempFile(stream, fileKey) {
403
- const ext = path.extname(fileKey) || ".bin";
404
- const tmpPath = path.join(os.tmpdir(), `better-media-${randomUUID()}${ext}`);
381
+ const ext = path2.extname(fileKey) || ".bin";
382
+ const tmpPath = path2.join(os.tmpdir(), `better-media-${randomUUID()}${ext}`);
405
383
  const nodeStream = stream instanceof Readable ? stream : Readable.fromWeb(stream);
406
384
  const writeStream = createWriteStream(tmpPath);
407
385
  await new Promise((resolve, reject) => {
@@ -419,6 +397,10 @@ async function loadFileIntoContext(context, fileHandling) {
419
397
  const storageWithExtras = storage;
420
398
  if (!context.utilities) context.utilities = {};
421
399
  const fileContent = {};
400
+ if (context.storageLocation.url && context.storageLocation.url === context.file.key) {
401
+ context.utilities.fileContent = fileContent;
402
+ return;
403
+ }
422
404
  let useStream = false;
423
405
  if (maxBufferBytes != null && typeof storageWithExtras.getSize === "function" && typeof storageWithExtras.getStream === "function") {
424
406
  const size = await storageWithExtras.getSize(fileKey);
@@ -459,8 +441,22 @@ async function cleanupTempFile(context) {
459
441
  function buildFileInfo(fileKey, metadata) {
460
442
  const mime = metadata.contentType ?? metadata.mimeType ?? metadata["content-type"];
461
443
  const size = typeof metadata.size === "number" ? metadata.size : void 0;
462
- const originalName = metadata.originalName ?? metadata.originalname;
463
- const ext = originalName ? path.extname(originalName).toLowerCase() : path.extname(fileKey).toLowerCase();
444
+ const originalName = metadata.originalName ?? metadata.originalname ?? (() => {
445
+ try {
446
+ const url = new URL(fileKey);
447
+ return path2.basename(url.pathname);
448
+ } catch {
449
+ return void 0;
450
+ }
451
+ })();
452
+ const ext = originalName ? path2.extname(originalName).toLowerCase() : (() => {
453
+ try {
454
+ const url = new URL(fileKey);
455
+ return path2.extname(url.pathname).toLowerCase();
456
+ } catch {
457
+ return path2.extname(fileKey).toLowerCase();
458
+ }
459
+ })();
464
460
  return {
465
461
  key: fileKey,
466
462
  size,
@@ -470,12 +466,12 @@ function buildFileInfo(fileKey, metadata) {
470
466
  checksums: void 0
471
467
  };
472
468
  }
473
- function buildStorageLocation(fileKey) {
469
+ function buildStorageLocation(fileKey, referenceUrl) {
474
470
  return {
475
471
  key: fileKey,
476
472
  bucket: void 0,
477
473
  region: void 0,
478
- url: void 0
474
+ url: referenceUrl
479
475
  };
480
476
  }
481
477
  function syncTrustedToFile(context) {
@@ -508,7 +504,7 @@ var PipelineExecutor = class {
508
504
  const context = {
509
505
  recordId,
510
506
  file: buildFileInfo(fileKey, meta),
511
- storageLocation: buildStorageLocation(fileKey),
507
+ storageLocation: buildStorageLocation(fileKey, appContext.referenceUrl),
512
508
  processing: {},
513
509
  metadata: { ...meta, ...appContext },
514
510
  // Merge for plugins to read backwards-compatibly
@@ -573,7 +569,7 @@ async function runBackgroundJob(payload, registry, storage, database, jobs, file
573
569
  size: typeof meta.size === "number" ? meta.size : void 0,
574
570
  mimeType: typeof (meta.contentType ?? meta.mimeType ?? meta["content-type"]) === "string" ? meta.contentType ?? meta.mimeType ?? meta["content-type"] : void 0,
575
571
  originalName: typeof (meta.originalName ?? meta.originalname) === "string" ? meta.originalName ?? meta.originalname : void 0,
576
- extension: path.extname(legacyKey).toLowerCase() || void 0
572
+ extension: path2.extname(legacyKey).toLowerCase() || void 0
577
573
  } : { key: "" });
578
574
  const recordId = payloadRecordId ?? file.key ?? "unknown";
579
575
  const storageLocation = payloadStorage ?? { key: file.key };
@@ -602,8 +598,7 @@ async function runBackgroundJob(payload, registry, storage, database, jobs, file
602
598
  throw new Error(`Handler not found: ${hookName}/${pluginName}`);
603
599
  }
604
600
  const manifest = handler.manifest;
605
- const { createSecureContext: createSecureContext2 } = await Promise.resolve().then(() => (init_lifecycle_engine(), lifecycle_engine_exports));
606
- const { proxy, api } = createSecureContext2(
601
+ const { proxy, api } = createSecureContext(
607
602
  context,
608
603
  pluginName,
609
604
  manifest.namespace,
@@ -905,7 +900,7 @@ async function normalizeInput(input, fileHandling) {
905
900
  data = Buffer.concat(chunks);
906
901
  } else if ("url" in file && file.url) {
907
902
  if (file.mode === "reference") {
908
- throw new Error("URL reference mode is not fully implemented yet.");
903
+ return { metadata, shouldDeleteSource: false, isReference: true, referenceUrl: file.url };
909
904
  }
910
905
  const response = await fetch(file.url);
911
906
  if (!response.ok) throw new Error(`Failed to fetch URL: ${response.statusText}`);
@@ -941,15 +936,19 @@ function createBetterMedia(config) {
941
936
  async ingest(input) {
942
937
  const normalized = await normalizeInput(input, fileHandling);
943
938
  const recordId = randomUUID();
944
- const finalKey = input.key ?? normalized.metadata.filename ?? recordId;
939
+ const finalKey = input.key ?? normalized.metadata.filename ?? (normalized.isReference ? normalized.referenceUrl : void 0) ?? recordId;
945
940
  try {
946
- await storage.put(finalKey, normalized.data);
947
- await runPipeline(
948
- recordId,
949
- finalKey,
950
- normalized.metadata,
951
- normalized.metadata.context ?? {}
952
- );
941
+ if (normalized.isReference && normalized.referenceUrl) {
942
+ normalized.metadata.referenceUrl = normalized.referenceUrl;
943
+ } else if (normalized.data) {
944
+ await storage.put(finalKey, normalized.data);
945
+ } else {
946
+ throw new Error("Ingest failed: No data and not a reference.");
947
+ }
948
+ await runPipeline(recordId, finalKey, normalized.metadata, {
949
+ ...normalized.metadata.context ?? {},
950
+ referenceUrl: normalized.referenceUrl
951
+ });
953
952
  return {
954
953
  id: recordId,
955
954
  key: finalKey,