@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/README.md CHANGED
@@ -46,4 +46,4 @@ console.log("Uploaded file key:", result.fileKey);
46
46
 
47
47
  ## Documentation
48
48
 
49
- Full documentation is available at [better-media.dev](https://better-media.dev).
49
+ Full documentation is available at [better-media-platform.vercel.app](https://better-media-platform.vercel.app/).
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var core = require('@better-media/core');
4
3
  var fs2 = require('fs/promises');
5
4
  var crypto = require('crypto');
6
5
  var adapterJobs = require('@better-media/adapter-jobs');
7
- var path = require('path');
6
+ var core = require('@better-media/core');
7
+ var path2 = require('path');
8
8
  var fs = require('fs');
9
9
  var os = require('os');
10
10
  var stream = require('stream');
@@ -12,25 +12,10 @@ var stream = require('stream');
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
13
 
14
14
  var fs2__default = /*#__PURE__*/_interopDefault(fs2);
15
- var path__default = /*#__PURE__*/_interopDefault(path);
15
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
16
16
  var os__default = /*#__PURE__*/_interopDefault(os);
17
17
 
18
- var __defProp = Object.defineProperty;
19
- var __getOwnPropNames = Object.getOwnPropertyNames;
20
- var __esm = (fn, res) => function __init() {
21
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
22
- };
23
- var __export = (target, all) => {
24
- for (var name in all)
25
- __defProp(target, name, { get: all[name], enumerable: true });
26
- };
27
-
28
- // src/core/lifecycle-engine.ts
29
- var lifecycle_engine_exports = {};
30
- __export(lifecycle_engine_exports, {
31
- LifecycleEngine: () => LifecycleEngine,
32
- createSecureContext: () => createSecureContext
33
- });
18
+ // src/index.ts
34
19
  function createSecureContext(context, pluginName, namespace, trustLevel, capabilities) {
35
20
  const api = {
36
21
  emitMetadata(patch) {
@@ -127,52 +112,48 @@ function createSecureContext(context, pluginName, namespace, trustLevel, capabil
127
112
  });
128
113
  return { proxy, api };
129
114
  }
130
- var JOB_QUEUE_NAME, LifecycleEngine;
131
- var init_lifecycle_engine = __esm({
132
- "src/core/lifecycle-engine.ts"() {
133
- JOB_QUEUE_NAME = "better-media:background";
134
- LifecycleEngine = class {
135
- constructor(registry, jobAdapter) {
136
- this.registry = registry;
137
- this.jobAdapter = jobAdapter;
138
- }
139
- async trigger(hookName, context) {
140
- const handlers = this.registry.get(hookName) ?? [];
141
- const syncHandlers = handlers.filter((h) => h.mode === "sync");
142
- const backgroundHandlers = handlers.filter((h) => h.mode === "background");
143
- for (const { name, fn, manifest } of syncHandlers) {
144
- const { proxy, api } = createSecureContext(
145
- context,
146
- name,
147
- manifest.namespace,
148
- manifest.trustLevel,
149
- manifest.capabilities
150
- );
151
- const result = await fn(proxy, api);
152
- if (result !== void 0 && typeof result === "object" && "valid" in result) {
153
- if (result.valid === false) return result;
154
- }
155
- }
156
- for (const { name, manifest } of backgroundHandlers) {
157
- const payload = {
158
- recordId: context.recordId,
159
- metadata: JSON.parse(JSON.stringify(context.metadata)),
160
- file: JSON.parse(JSON.stringify(context.file)),
161
- storageLocation: JSON.parse(JSON.stringify(context.storageLocation)),
162
- processing: JSON.parse(JSON.stringify(context.processing)),
163
- hookName,
164
- pluginName: name,
165
- manifest: JSON.parse(JSON.stringify(manifest))
166
- };
167
- await this.jobAdapter.enqueue(JOB_QUEUE_NAME, payload);
168
- }
115
+
116
+ // src/core/lifecycle-engine.ts
117
+ var JOB_QUEUE_NAME = "better-media:background";
118
+ var LifecycleEngine = class {
119
+ constructor(registry, jobAdapter) {
120
+ this.registry = registry;
121
+ this.jobAdapter = jobAdapter;
122
+ }
123
+ async trigger(hookName, context) {
124
+ const handlers = this.registry.get(hookName) ?? [];
125
+ const syncHandlers = handlers.filter((h) => h.mode === "sync");
126
+ const backgroundHandlers = handlers.filter((h) => h.mode === "background");
127
+ for (const { name, fn, manifest } of syncHandlers) {
128
+ const { proxy, api } = createSecureContext(
129
+ context,
130
+ name,
131
+ manifest.namespace,
132
+ manifest.trustLevel,
133
+ manifest.capabilities
134
+ );
135
+ const result = await fn(proxy, api);
136
+ if (result !== void 0 && typeof result === "object" && "valid" in result) {
137
+ if (result.valid === false) return result;
169
138
  }
170
- };
139
+ }
140
+ for (const { name, manifest } of backgroundHandlers) {
141
+ const payload = {
142
+ recordId: context.recordId,
143
+ metadata: JSON.parse(JSON.stringify(context.metadata)),
144
+ file: JSON.parse(JSON.stringify(context.file)),
145
+ storageLocation: JSON.parse(JSON.stringify(context.storageLocation)),
146
+ processing: JSON.parse(JSON.stringify(context.processing)),
147
+ hookName,
148
+ pluginName: name,
149
+ manifest: JSON.parse(JSON.stringify(manifest))
150
+ };
151
+ await this.jobAdapter.enqueue(JOB_QUEUE_NAME, payload);
152
+ }
171
153
  }
172
- });
154
+ };
173
155
 
174
156
  // src/plugins/plugin-registry.ts
175
- init_lifecycle_engine();
176
157
  function createEmptyRegistry() {
177
158
  const reg = /* @__PURE__ */ new Map();
178
159
  for (const name of core.HOOK_NAMES) {
@@ -336,9 +317,6 @@ function hasBackgroundHandlers(registry) {
336
317
  }
337
318
  return false;
338
319
  }
339
-
340
- // src/index.ts
341
- init_lifecycle_engine();
342
320
  async function loadTrustedFromDb(database, recordId) {
343
321
  const record = await database.findOne({
344
322
  model: "media",
@@ -407,8 +385,8 @@ async function saveTrustedToDb(database, recordId, fileKey, trusted, initialArgs
407
385
  }
408
386
  }
409
387
  async function streamToTempFile(stream$1, fileKey) {
410
- const ext = path__default.default.extname(fileKey) || ".bin";
411
- const tmpPath = path__default.default.join(os__default.default.tmpdir(), `better-media-${crypto.randomUUID()}${ext}`);
388
+ const ext = path2__default.default.extname(fileKey) || ".bin";
389
+ const tmpPath = path2__default.default.join(os__default.default.tmpdir(), `better-media-${crypto.randomUUID()}${ext}`);
412
390
  const nodeStream = stream$1 instanceof stream.Readable ? stream$1 : stream.Readable.fromWeb(stream$1);
413
391
  const writeStream = fs.createWriteStream(tmpPath);
414
392
  await new Promise((resolve, reject) => {
@@ -426,6 +404,10 @@ async function loadFileIntoContext(context, fileHandling) {
426
404
  const storageWithExtras = storage;
427
405
  if (!context.utilities) context.utilities = {};
428
406
  const fileContent = {};
407
+ if (context.storageLocation.url && context.storageLocation.url === context.file.key) {
408
+ context.utilities.fileContent = fileContent;
409
+ return;
410
+ }
429
411
  let useStream = false;
430
412
  if (maxBufferBytes != null && typeof storageWithExtras.getSize === "function" && typeof storageWithExtras.getStream === "function") {
431
413
  const size = await storageWithExtras.getSize(fileKey);
@@ -466,8 +448,22 @@ async function cleanupTempFile(context) {
466
448
  function buildFileInfo(fileKey, metadata) {
467
449
  const mime = metadata.contentType ?? metadata.mimeType ?? metadata["content-type"];
468
450
  const size = typeof metadata.size === "number" ? metadata.size : void 0;
469
- const originalName = metadata.originalName ?? metadata.originalname;
470
- const ext = originalName ? path__default.default.extname(originalName).toLowerCase() : path__default.default.extname(fileKey).toLowerCase();
451
+ const originalName = metadata.originalName ?? metadata.originalname ?? (() => {
452
+ try {
453
+ const url = new URL(fileKey);
454
+ return path2__default.default.basename(url.pathname);
455
+ } catch {
456
+ return void 0;
457
+ }
458
+ })();
459
+ const ext = originalName ? path2__default.default.extname(originalName).toLowerCase() : (() => {
460
+ try {
461
+ const url = new URL(fileKey);
462
+ return path2__default.default.extname(url.pathname).toLowerCase();
463
+ } catch {
464
+ return path2__default.default.extname(fileKey).toLowerCase();
465
+ }
466
+ })();
471
467
  return {
472
468
  key: fileKey,
473
469
  size,
@@ -477,12 +473,12 @@ function buildFileInfo(fileKey, metadata) {
477
473
  checksums: void 0
478
474
  };
479
475
  }
480
- function buildStorageLocation(fileKey) {
476
+ function buildStorageLocation(fileKey, referenceUrl) {
481
477
  return {
482
478
  key: fileKey,
483
479
  bucket: void 0,
484
480
  region: void 0,
485
- url: void 0
481
+ url: referenceUrl
486
482
  };
487
483
  }
488
484
  function syncTrustedToFile(context) {
@@ -515,7 +511,7 @@ var PipelineExecutor = class {
515
511
  const context = {
516
512
  recordId,
517
513
  file: buildFileInfo(fileKey, meta),
518
- storageLocation: buildStorageLocation(fileKey),
514
+ storageLocation: buildStorageLocation(fileKey, appContext.referenceUrl),
519
515
  processing: {},
520
516
  metadata: { ...meta, ...appContext },
521
517
  // Merge for plugins to read backwards-compatibly
@@ -580,7 +576,7 @@ async function runBackgroundJob(payload, registry, storage, database, jobs, file
580
576
  size: typeof meta.size === "number" ? meta.size : void 0,
581
577
  mimeType: typeof (meta.contentType ?? meta.mimeType ?? meta["content-type"]) === "string" ? meta.contentType ?? meta.mimeType ?? meta["content-type"] : void 0,
582
578
  originalName: typeof (meta.originalName ?? meta.originalname) === "string" ? meta.originalName ?? meta.originalname : void 0,
583
- extension: path__default.default.extname(legacyKey).toLowerCase() || void 0
579
+ extension: path2__default.default.extname(legacyKey).toLowerCase() || void 0
584
580
  } : { key: "" });
585
581
  const recordId = payloadRecordId ?? file.key ?? "unknown";
586
582
  const storageLocation = payloadStorage ?? { key: file.key };
@@ -609,8 +605,7 @@ async function runBackgroundJob(payload, registry, storage, database, jobs, file
609
605
  throw new Error(`Handler not found: ${hookName}/${pluginName}`);
610
606
  }
611
607
  const manifest = handler.manifest;
612
- const { createSecureContext: createSecureContext2 } = await Promise.resolve().then(() => (init_lifecycle_engine(), lifecycle_engine_exports));
613
- const { proxy, api } = createSecureContext2(
608
+ const { proxy, api } = createSecureContext(
614
609
  context,
615
610
  pluginName,
616
611
  manifest.namespace,
@@ -912,7 +907,7 @@ async function normalizeInput(input, fileHandling) {
912
907
  data = Buffer.concat(chunks);
913
908
  } else if ("url" in file && file.url) {
914
909
  if (file.mode === "reference") {
915
- throw new Error("URL reference mode is not fully implemented yet.");
910
+ return { metadata, shouldDeleteSource: false, isReference: true, referenceUrl: file.url };
916
911
  }
917
912
  const response = await fetch(file.url);
918
913
  if (!response.ok) throw new Error(`Failed to fetch URL: ${response.statusText}`);
@@ -948,15 +943,19 @@ function createBetterMedia(config) {
948
943
  async ingest(input) {
949
944
  const normalized = await normalizeInput(input, fileHandling);
950
945
  const recordId = crypto.randomUUID();
951
- const finalKey = input.key ?? normalized.metadata.filename ?? recordId;
946
+ const finalKey = input.key ?? normalized.metadata.filename ?? (normalized.isReference ? normalized.referenceUrl : void 0) ?? recordId;
952
947
  try {
953
- await storage.put(finalKey, normalized.data);
954
- await runPipeline(
955
- recordId,
956
- finalKey,
957
- normalized.metadata,
958
- normalized.metadata.context ?? {}
959
- );
948
+ if (normalized.isReference && normalized.referenceUrl) {
949
+ normalized.metadata.referenceUrl = normalized.referenceUrl;
950
+ } else if (normalized.data) {
951
+ await storage.put(finalKey, normalized.data);
952
+ } else {
953
+ throw new Error("Ingest failed: No data and not a reference.");
954
+ }
955
+ await runPipeline(recordId, finalKey, normalized.metadata, {
956
+ ...normalized.metadata.context ?? {},
957
+ referenceUrl: normalized.referenceUrl
958
+ });
960
959
  return {
961
960
  id: recordId,
962
961
  key: finalKey,