@holo-js/media 0.3.16 → 0.3.18
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.d.ts +11 -3
- package/dist/index.mjs +283 -354
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -185,7 +185,7 @@ declare const Media: _holo_js_db.StaticModelApi<_holo_js_db.BoundTableDefinition
|
|
|
185
185
|
updated_at: _holo_js_db.ColumnBuilder<Date, false, true, false, false>;
|
|
186
186
|
}>, _holo_js_db.EmptyScopeMap, {
|
|
187
187
|
model: _holo_js_db.MorphToRelationDefinition<_holo_js_db.TableDefinition<string, _holo_js_db.TableColumnsShape>>;
|
|
188
|
-
}>;
|
|
188
|
+
}, readonly string[]>;
|
|
189
189
|
|
|
190
190
|
type MediaRecord = ModelRecord<typeof Media.definition.table>;
|
|
191
191
|
declare class MediaItem<TCollectionName extends string = string, TConversionName extends string = string, TEntity extends Entity<TableDefinition> = Entity<TableDefinition>> {
|
|
@@ -209,7 +209,6 @@ declare class MediaItem<TCollectionName extends string = string, TConversionName
|
|
|
209
209
|
regenerate(conversions?: TConversionName | readonly TConversionName[]): Promise<this>;
|
|
210
210
|
toJSON(): MediaRecord;
|
|
211
211
|
private resolveVariant;
|
|
212
|
-
private resolveStoredVariants;
|
|
213
212
|
}
|
|
214
213
|
|
|
215
214
|
type MediaSourceInput = string | BinaryContent | {
|
|
@@ -368,5 +367,14 @@ declare function dispatchQueuedMediaConversions(payload: MediaGenerateConversion
|
|
|
368
367
|
|
|
369
368
|
declare function createDefaultMediaConversionExecutor(): MediaConversionExecutor;
|
|
370
369
|
declare const defaultMediaConversionExecutor: MediaConversionExecutor;
|
|
370
|
+
interface MediaImageInspection {
|
|
371
|
+
readonly mimeType: 'image/gif' | 'image/jpeg' | 'image/png' | 'image/webp';
|
|
372
|
+
readonly width: number;
|
|
373
|
+
readonly height: number;
|
|
374
|
+
readonly pages: number;
|
|
375
|
+
}
|
|
376
|
+
declare const mediaRuntimeInternals: Readonly<{
|
|
377
|
+
inspectImage(contents: Uint8Array, maximumPixels?: number): Promise<MediaImageInspection>;
|
|
378
|
+
}>;
|
|
371
379
|
|
|
372
|
-
export { type CollectionNamesOf, type ConversionNamesOf, MEDIA_GENERATE_CONVERSIONS_JOB, Media, type MediaAddResult, MediaAdder, type MediaCollectionBuilder, type MediaCollectionDefinition, type MediaConversionBuilder, type MediaConversionDefinition, type MediaConversionExecutor, type MediaConversionExecutorInput, type MediaConversionExecutorResult, type MediaConversionExecutorSource, type MediaConversionFit, type MediaConversionFormat, type MediaConversionPathInput, type MediaDefinitionFactory, type MediaDefinitionHelpers, type MediaDefinitionInput, type MediaEnabledEntity, type MediaEnabledModel, type MediaGenerateConversionsPayload, type MediaGenerateConversionsResult, MediaItem, type MediaOriginalPathInput, type MediaPathGenerator, type MediaSourceInput, type NormalizedMediaCollectionDefinition, type NormalizedMediaConversionDefinition, type NormalizedMediaDefinition, type StoredMediaConversion, collection, conversion, createDefaultMediaConversionExecutor, defaultMediaConversionExecutor, defineMediaModel, dispatchQueuedMediaConversions, ensureMediaQueueJobRegistered, getMediaConversionExecutor, getMediaDefinition, getMediaDefinitionForMorphClass, getMediaPathGenerator, normalizeCollectionDefinitions, normalizeConversionDefinitions, normalizeMediaDefinition, registerMediaDefinition, requireMediaDefinition, requireMediaDefinitionForMorphClass, resetMediaPathGenerator, resetMediaRuntime, resolveMediaCollection, resolveMediaConversion, resolveMediaDefinition, runMediaGenerateConversionsJob, setMediaConversionExecutor, setMediaPathGenerator };
|
|
380
|
+
export { type CollectionNamesOf, type ConversionNamesOf, MEDIA_GENERATE_CONVERSIONS_JOB, Media, type MediaAddResult, MediaAdder, type MediaCollectionBuilder, type MediaCollectionDefinition, type MediaConversionBuilder, type MediaConversionDefinition, type MediaConversionExecutor, type MediaConversionExecutorInput, type MediaConversionExecutorResult, type MediaConversionExecutorSource, type MediaConversionFit, type MediaConversionFormat, type MediaConversionPathInput, type MediaDefinitionFactory, type MediaDefinitionHelpers, type MediaDefinitionInput, type MediaEnabledEntity, type MediaEnabledModel, type MediaGenerateConversionsPayload, type MediaGenerateConversionsResult, MediaItem, type MediaOriginalPathInput, type MediaPathGenerator, type MediaSourceInput, type NormalizedMediaCollectionDefinition, type NormalizedMediaConversionDefinition, type NormalizedMediaDefinition, type StoredMediaConversion, collection, conversion, createDefaultMediaConversionExecutor, defaultMediaConversionExecutor, defineMediaModel, dispatchQueuedMediaConversions, ensureMediaQueueJobRegistered, getMediaConversionExecutor, getMediaDefinition, getMediaDefinitionForMorphClass, getMediaPathGenerator, mediaRuntimeInternals, normalizeCollectionDefinitions, normalizeConversionDefinitions, normalizeMediaDefinition, registerMediaDefinition, requireMediaDefinition, requireMediaDefinitionForMorphClass, resetMediaPathGenerator, resetMediaRuntime, resolveMediaCollection, resolveMediaConversion, resolveMediaDefinition, runMediaGenerateConversionsJob, setMediaConversionExecutor, setMediaPathGenerator };
|
package/dist/index.mjs
CHANGED
|
@@ -110,10 +110,7 @@ function replaceFileExtension(fileName, extension) {
|
|
|
110
110
|
// src/runtime/image.ts
|
|
111
111
|
var sharpFactoryPromise;
|
|
112
112
|
async function loadSharp() {
|
|
113
|
-
sharpFactoryPromise ??= import("sharp").then((module) => {
|
|
114
|
-
const resolved = module;
|
|
115
|
-
return resolved.default ?? resolved;
|
|
116
|
-
}).catch((error) => {
|
|
113
|
+
sharpFactoryPromise ??= import("sharp").then((module) => module.default).catch((error) => {
|
|
117
114
|
sharpFactoryPromise = void 0;
|
|
118
115
|
throw error;
|
|
119
116
|
});
|
|
@@ -195,6 +192,30 @@ function createDefaultMediaConversionExecutor() {
|
|
|
195
192
|
};
|
|
196
193
|
}
|
|
197
194
|
var defaultMediaConversionExecutor = createDefaultMediaConversionExecutor();
|
|
195
|
+
var imageMimeTypes = {
|
|
196
|
+
gif: "image/gif",
|
|
197
|
+
jpeg: "image/jpeg",
|
|
198
|
+
png: "image/png",
|
|
199
|
+
webp: "image/webp"
|
|
200
|
+
};
|
|
201
|
+
var mediaRuntimeInternals = Object.freeze({
|
|
202
|
+
async inspectImage(contents, maximumPixels = 4e7) {
|
|
203
|
+
if (!Number.isSafeInteger(maximumPixels) || maximumPixels < 1) throw new TypeError("[Holo Media] Image pixel limits require a positive integer.");
|
|
204
|
+
const factory = await loadSharp();
|
|
205
|
+
try {
|
|
206
|
+
const image = factory(Buffer.from(contents), { animated: true, failOn: "warning", limitInputPixels: maximumPixels });
|
|
207
|
+
const metadata = await image.metadata();
|
|
208
|
+
const { width, height } = metadata;
|
|
209
|
+
const format = metadata.format;
|
|
210
|
+
if (format !== "gif" && format !== "jpeg" && format !== "png" && format !== "webp") throw new Error("[Holo Media] Unsupported raster image format.");
|
|
211
|
+
if (!width || !height || !Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width * height > maximumPixels) throw new Error("[Holo Media] Image exceeds the pixel limit.");
|
|
212
|
+
await image.stats();
|
|
213
|
+
return { mimeType: imageMimeTypes[format], width, height: metadata.pageHeight ?? height, pages: metadata.pages ?? 1 };
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw new Error("[Holo Media] Image could not be decoded.", { cause: error });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
198
219
|
|
|
199
220
|
// src/registry.ts
|
|
200
221
|
var mediaRegistrySymbol = /* @__PURE__ */ Symbol.for("@holo-js/media.registry");
|
|
@@ -384,8 +405,7 @@ import { BlockList, isIP } from "net";
|
|
|
384
405
|
import { tmpdir } from "os";
|
|
385
406
|
import { basename as basename2, resolve, sep } from "path";
|
|
386
407
|
import { Readable } from "stream";
|
|
387
|
-
import { Storage as
|
|
388
|
-
import { connectionAsyncContext as connectionAsyncContext4 } from "@holo-js/db";
|
|
408
|
+
import { Storage as Storage4 } from "@holo-js/storage/runtime";
|
|
389
409
|
|
|
390
410
|
// src/queue.ts
|
|
391
411
|
import {
|
|
@@ -394,59 +414,114 @@ import {
|
|
|
394
414
|
getRegisteredQueueJob,
|
|
395
415
|
registerQueueJob
|
|
396
416
|
} from "@holo-js/queue";
|
|
397
|
-
import { connectionAsyncContext
|
|
417
|
+
import { connectionAsyncContext } from "@holo-js/db";
|
|
398
418
|
|
|
399
419
|
// src/model/conversions.ts
|
|
420
|
+
import { Storage as Storage2 } from "@holo-js/storage/runtime";
|
|
421
|
+
|
|
422
|
+
// src/model/mutation.ts
|
|
423
|
+
import { DB } from "@holo-js/db";
|
|
400
424
|
import { Storage } from "@holo-js/storage/runtime";
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
425
|
+
var MediaMutation = class {
|
|
426
|
+
written = [];
|
|
427
|
+
async put(disk, path, contents) {
|
|
428
|
+
const previous = await Storage.disk(disk).getBytes(path);
|
|
429
|
+
this.written.push({ disk, path, previous });
|
|
430
|
+
await Storage.disk(disk).put(path, contents);
|
|
431
|
+
}
|
|
432
|
+
async compensate() {
|
|
433
|
+
const failures = [];
|
|
434
|
+
for (const file of this.written.splice(0).reverse()) {
|
|
435
|
+
try {
|
|
436
|
+
if (file.previous) {
|
|
437
|
+
await Storage.disk(file.disk).put(file.path, file.previous);
|
|
438
|
+
} else {
|
|
439
|
+
await Storage.disk(file.disk).delete(file.path);
|
|
440
|
+
}
|
|
441
|
+
} catch (error) {
|
|
442
|
+
failures.push(error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (failures.length === 1) throw failures[0];
|
|
446
|
+
if (failures.length > 1) throw new AggregateError(failures, "[Holo Media] File compensation failed.");
|
|
413
447
|
}
|
|
414
|
-
|
|
415
|
-
|
|
448
|
+
};
|
|
449
|
+
async function runMediaMutation(options) {
|
|
450
|
+
const mutation = new MediaMutation();
|
|
451
|
+
return await DB.writeTransaction(async (transaction) => {
|
|
452
|
+
transaction.afterRollback(async () => {
|
|
453
|
+
const failures = [];
|
|
454
|
+
try {
|
|
455
|
+
await mutation.compensate();
|
|
456
|
+
} catch (error) {
|
|
457
|
+
failures.push(error);
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
await options.afterRollback?.();
|
|
461
|
+
} catch (error) {
|
|
462
|
+
failures.push(error);
|
|
463
|
+
}
|
|
464
|
+
if (failures.length === 1) throw failures[0];
|
|
465
|
+
if (failures.length > 1) throw new AggregateError(failures, "[Holo Media] Rollback restoration failed.");
|
|
466
|
+
});
|
|
467
|
+
const result = await options.operation(mutation);
|
|
468
|
+
transaction.afterCommit(async () => {
|
|
469
|
+
const failures = [];
|
|
470
|
+
for (const effect of options.afterCommit ?? []) {
|
|
471
|
+
try {
|
|
472
|
+
await effect(result);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
failures.push(error);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
options.afterEffects?.();
|
|
478
|
+
if (failures.length === 0) return;
|
|
479
|
+
const cause = failures.length === 1 ? failures[0] : new AggregateError(failures, "[Holo Media] Post-commit effects failed.");
|
|
480
|
+
throw new Error(options.committedMessage, { cause });
|
|
481
|
+
});
|
|
482
|
+
return result;
|
|
416
483
|
});
|
|
417
484
|
}
|
|
418
|
-
async function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
await restoreStoredFileSnapshot(snapshot).catch(() => void 0);
|
|
485
|
+
async function removeStoredFiles(files, retained = []) {
|
|
486
|
+
const failures = [];
|
|
487
|
+
for (const file of files) {
|
|
488
|
+
if (retained.some((current) => current.disk === file.disk && current.path === file.path)) continue;
|
|
489
|
+
try {
|
|
490
|
+
await Storage.disk(file.disk).delete(file.path);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
failures.push(error);
|
|
493
|
+
}
|
|
428
494
|
}
|
|
495
|
+
if (failures.length === 1) throw failures[0];
|
|
496
|
+
if (failures.length > 1) throw new AggregateError(failures, "[Holo Media] Obsolete-file cleanup failed.");
|
|
429
497
|
}
|
|
430
|
-
async function
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
498
|
+
async function removeObsoleteFiles(items, retained) {
|
|
499
|
+
await removeStoredFiles(items.flatMap(({ record }) => [
|
|
500
|
+
{ disk: record.disk, path: record.path },
|
|
501
|
+
...Object.values(record.generated_conversions ?? {}).filter((conversion2) => conversion2?.path).map((conversion2) => ({
|
|
502
|
+
disk: conversion2.disk ?? record.conversions_disk ?? record.disk,
|
|
503
|
+
path: conversion2.path
|
|
504
|
+
}))
|
|
505
|
+
]), retained);
|
|
506
|
+
}
|
|
507
|
+
async function deleteMediaItems(items, owner) {
|
|
508
|
+
if (items.length === 0) return;
|
|
509
|
+
await runMediaMutation({
|
|
510
|
+
committedMessage: "[Holo Media] Deletion remains committed; post-commit file cleanup failed.",
|
|
511
|
+
operation: async () => {
|
|
512
|
+
for (const item of items) await item.getEntity().delete();
|
|
513
|
+
owner?.forgetRelation("media");
|
|
514
|
+
},
|
|
515
|
+
afterRollback: () => {
|
|
516
|
+
owner?.forgetRelation("media");
|
|
517
|
+
},
|
|
518
|
+
afterCommit: [async () => {
|
|
519
|
+
await removeObsoleteFiles(items, []);
|
|
520
|
+
}]
|
|
448
521
|
});
|
|
449
522
|
}
|
|
523
|
+
|
|
524
|
+
// src/model/conversions.ts
|
|
450
525
|
function fallbackCollectionDefinition(collectionName) {
|
|
451
526
|
return Object.freeze({
|
|
452
527
|
kind: "collection",
|
|
@@ -526,58 +601,45 @@ async function generateStoredConversions(options) {
|
|
|
526
601
|
}
|
|
527
602
|
const generatedConversions = /* @__PURE__ */ Object.create(null);
|
|
528
603
|
const executor = getMediaConversionExecutor();
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if (!generated) {
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
const generatedFileName = sanitizeFileName(generated.fileName ?? options.source.fileName);
|
|
541
|
-
const conversionPath = getMediaPathGenerator().conversionPath({
|
|
542
|
-
uuid: options.source.uuid,
|
|
543
|
-
fileName: options.source.fileName,
|
|
544
|
-
extension: options.source.extension,
|
|
545
|
-
collection: options.collection,
|
|
546
|
-
conversion: conversion2,
|
|
547
|
-
generatedFileName
|
|
548
|
-
});
|
|
549
|
-
const targetDisk = generated.disk ?? options.conversionsDisk;
|
|
550
|
-
const conversionMimeType = inferMimeType(generatedFileName, generated.mimeType);
|
|
551
|
-
const conversionContents = await toBinaryContent(generated.contents);
|
|
552
|
-
writtenSnapshots.push(await putFileWithRollbackRestore(targetDisk, conversionPath, conversionContents));
|
|
553
|
-
generatedConversions[conversion2.name] = Object.freeze({
|
|
554
|
-
path: conversionPath,
|
|
555
|
-
disk: targetDisk,
|
|
556
|
-
fileName: generatedFileName,
|
|
557
|
-
mimeType: conversionMimeType,
|
|
558
|
-
size: getContentSize(conversionContents)
|
|
559
|
-
});
|
|
604
|
+
for (const conversion2 of matchingConversions) {
|
|
605
|
+
const generated = await executor.generate({
|
|
606
|
+
source: options.source,
|
|
607
|
+
collection: options.collection,
|
|
608
|
+
conversion: conversion2
|
|
609
|
+
});
|
|
610
|
+
if (!generated) {
|
|
611
|
+
continue;
|
|
560
612
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
613
|
+
const generatedFileName = sanitizeFileName(generated.fileName ?? options.source.fileName);
|
|
614
|
+
const conversionPath = getMediaPathGenerator().conversionPath({
|
|
615
|
+
uuid: options.source.uuid,
|
|
616
|
+
fileName: options.source.fileName,
|
|
617
|
+
extension: options.source.extension,
|
|
618
|
+
collection: options.collection,
|
|
619
|
+
conversion: conversion2,
|
|
620
|
+
generatedFileName
|
|
621
|
+
});
|
|
622
|
+
const targetDisk = generated.disk ?? options.conversionsDisk;
|
|
623
|
+
const conversionMimeType = inferMimeType(generatedFileName, generated.mimeType);
|
|
624
|
+
const conversionContents = await toBinaryContent(generated.contents);
|
|
625
|
+
await options.mutation.put(targetDisk, conversionPath, conversionContents);
|
|
626
|
+
generatedConversions[conversion2.name] = Object.freeze({
|
|
627
|
+
path: conversionPath,
|
|
628
|
+
disk: targetDisk,
|
|
629
|
+
fileName: generatedFileName,
|
|
630
|
+
mimeType: conversionMimeType,
|
|
631
|
+
size: getContentSize(conversionContents)
|
|
632
|
+
});
|
|
564
633
|
}
|
|
565
634
|
return Object.freeze(generatedConversions);
|
|
566
635
|
}
|
|
567
636
|
async function deleteObsoleteConversions(current, next, fallbackDisk, requested) {
|
|
568
|
-
|
|
569
|
-
if (requested?.length && !requested.includes(name))
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
if (!conversion2?.path) {
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
637
|
+
const obsolete = Object.entries(current).filter(([name, conversion2]) => {
|
|
638
|
+
if (requested?.length && !requested.includes(name) || !conversion2?.path) return false;
|
|
575
639
|
const nextConversion = next[name];
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
await deleteFileWithRollbackRestore(conversion2.disk ?? fallbackDisk, conversion2.path);
|
|
580
|
-
}
|
|
640
|
+
return nextConversion?.path !== conversion2.path || (nextConversion.disk ?? fallbackDisk) !== (conversion2.disk ?? fallbackDisk);
|
|
641
|
+
}).map(([, conversion2]) => ({ disk: conversion2.disk ?? fallbackDisk, path: conversion2.path }));
|
|
642
|
+
await removeStoredFiles(obsolete);
|
|
581
643
|
}
|
|
582
644
|
async function regenerateMediaEntityConversions(options) {
|
|
583
645
|
const media = options.media;
|
|
@@ -591,7 +653,7 @@ async function regenerateMediaEntityConversions(options) {
|
|
|
591
653
|
resolveMatchingConversions(definition, collectionName, requested, {
|
|
592
654
|
includeQueued: options.includeQueued === true
|
|
593
655
|
});
|
|
594
|
-
const sourceContents = await
|
|
656
|
+
const sourceContents = await Storage2.disk(String(media.get("disk"))).getBytes(String(media.get("path")));
|
|
595
657
|
if (!sourceContents) {
|
|
596
658
|
throw new Error(
|
|
597
659
|
`[Holo Media] Cannot regenerate conversions for media "${media.get("uuid")}" because the original file is missing.`
|
|
@@ -600,32 +662,53 @@ async function regenerateMediaEntityConversions(options) {
|
|
|
600
662
|
const nextBase = requested?.length ? Object.fromEntries(
|
|
601
663
|
Object.entries(current).filter(([name]) => !requested.includes(name))
|
|
602
664
|
) : {};
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
665
|
+
return await runMediaMutation({
|
|
666
|
+
committedMessage: "[Holo Media] Regeneration remains committed; post-commit cleanup or conversion dispatch failed.",
|
|
667
|
+
afterRollback: async () => {
|
|
668
|
+
options.owner?.forgetRelation("media");
|
|
669
|
+
await media.refresh();
|
|
670
|
+
},
|
|
671
|
+
operation: async (mutation) => {
|
|
672
|
+
const regenerated = await generateStoredConversions({
|
|
673
|
+
definition,
|
|
674
|
+
collection: collection2,
|
|
675
|
+
conversionsDisk,
|
|
676
|
+
requestedConversions: requested,
|
|
677
|
+
includeQueued: options.includeQueued === true,
|
|
678
|
+
mutation,
|
|
679
|
+
source: {
|
|
680
|
+
uuid: String(media.get("uuid")),
|
|
681
|
+
fileName: String(media.get("file_name")),
|
|
682
|
+
extension: media.get("extension") ?? void 0,
|
|
683
|
+
mimeType: media.get("mime_type") ?? void 0,
|
|
684
|
+
size: sourceContents.byteLength,
|
|
685
|
+
contents: sourceContents
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
const generatedConversions = Object.freeze({ ...nextBase, ...regenerated });
|
|
689
|
+
media.forceFill({ generated_conversions: generatedConversions });
|
|
690
|
+
await media.save();
|
|
691
|
+
options.owner?.forgetRelation("media");
|
|
692
|
+
return generatedConversions;
|
|
693
|
+
},
|
|
694
|
+
afterCommit: [
|
|
695
|
+
async (generatedConversions) => {
|
|
696
|
+
await deleteObsoleteConversions(current, generatedConversions, conversionsDisk, requested);
|
|
697
|
+
},
|
|
698
|
+
async () => {
|
|
699
|
+
if (options.includeQueued) return;
|
|
700
|
+
await dispatchQueuedMediaConversionsForModel({
|
|
701
|
+
mediaId: media.get("id"),
|
|
702
|
+
conversionNames: resolveQueuedConversionNames({ definition, collectionName, requestedConversions: requested })
|
|
703
|
+
}, async () => {
|
|
704
|
+
await media.refresh();
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
],
|
|
708
|
+
afterEffects: () => {
|
|
709
|
+
options.owner?.forgetRelation("media");
|
|
616
710
|
}
|
|
617
711
|
});
|
|
618
|
-
const generatedConversions = Object.freeze({
|
|
619
|
-
...nextBase,
|
|
620
|
-
...regenerated
|
|
621
|
-
});
|
|
622
|
-
media.forceFill({
|
|
623
|
-
generated_conversions: generatedConversions
|
|
624
|
-
});
|
|
625
|
-
await media.save();
|
|
626
|
-
await deleteObsoleteConversions(current, generatedConversions, conversionsDisk, requested);
|
|
627
|
-
options.owner?.forgetRelation("media");
|
|
628
|
-
return generatedConversions;
|
|
629
712
|
}
|
|
630
713
|
|
|
631
714
|
// src/queue.ts
|
|
@@ -698,8 +781,8 @@ function ensureMediaQueueJobRegistered() {
|
|
|
698
781
|
name: MEDIA_GENERATE_CONVERSIONS_JOB
|
|
699
782
|
});
|
|
700
783
|
}
|
|
701
|
-
function
|
|
702
|
-
const active =
|
|
784
|
+
function getActiveTransaction() {
|
|
785
|
+
const active = connectionAsyncContext.getActive()?.connection;
|
|
703
786
|
if (!active || active.getScope().kind === "root") {
|
|
704
787
|
return void 0;
|
|
705
788
|
}
|
|
@@ -717,7 +800,7 @@ async function dispatchQueuedMediaConversions(payload) {
|
|
|
717
800
|
return;
|
|
718
801
|
}
|
|
719
802
|
ensureMediaQueueJobRegistered();
|
|
720
|
-
const activeTransaction =
|
|
803
|
+
const activeTransaction = getActiveTransaction();
|
|
721
804
|
if (activeTransaction) {
|
|
722
805
|
activeTransaction.afterCommit(() => executeQueuedMediaDispatch(normalized));
|
|
723
806
|
return;
|
|
@@ -730,7 +813,7 @@ async function dispatchQueuedMediaConversionsForModel(payload, onSynchronousDisp
|
|
|
730
813
|
return;
|
|
731
814
|
}
|
|
732
815
|
ensureMediaQueueJobRegistered();
|
|
733
|
-
const activeTransaction =
|
|
816
|
+
const activeTransaction = getActiveTransaction();
|
|
734
817
|
if (activeTransaction) {
|
|
735
818
|
activeTransaction.afterCommit(() => executeQueuedMediaDispatch(normalized, onSynchronousDispatch));
|
|
736
819
|
return;
|
|
@@ -739,8 +822,7 @@ async function dispatchQueuedMediaConversionsForModel(payload, onSynchronousDisp
|
|
|
739
822
|
}
|
|
740
823
|
|
|
741
824
|
// src/model/item.ts
|
|
742
|
-
import { Storage as
|
|
743
|
-
import { connectionAsyncContext as connectionAsyncContext3 } from "@holo-js/db";
|
|
825
|
+
import { Storage as Storage3 } from "@holo-js/storage/runtime";
|
|
744
826
|
function toUrlPath(url) {
|
|
745
827
|
if (url.startsWith("/")) {
|
|
746
828
|
return url;
|
|
@@ -752,17 +834,6 @@ function toUrlPath(url) {
|
|
|
752
834
|
return `/${url.replace(/^\/+/, "")}`;
|
|
753
835
|
}
|
|
754
836
|
}
|
|
755
|
-
function registerDeletedVariantRollbackRestore(snapshots) {
|
|
756
|
-
const active = connectionAsyncContext3.getActive()?.connection;
|
|
757
|
-
if (!active || active.getScope().kind === "root") {
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
active.afterRollback(async () => {
|
|
761
|
-
for (const snapshot of [...snapshots].reverse()) {
|
|
762
|
-
await Storage2.disk(snapshot.disk).put(snapshot.path, snapshot.contents).catch(() => void 0);
|
|
763
|
-
}
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
837
|
var MediaItem = class {
|
|
767
838
|
constructor(entity, owner) {
|
|
768
839
|
this.entity = entity;
|
|
@@ -802,7 +873,7 @@ var MediaItem = class {
|
|
|
802
873
|
return null;
|
|
803
874
|
}
|
|
804
875
|
try {
|
|
805
|
-
return
|
|
876
|
+
return Storage3.disk(variant.disk).url(variant.path);
|
|
806
877
|
} catch {
|
|
807
878
|
return null;
|
|
808
879
|
}
|
|
@@ -813,34 +884,13 @@ var MediaItem = class {
|
|
|
813
884
|
return null;
|
|
814
885
|
}
|
|
815
886
|
try {
|
|
816
|
-
return
|
|
887
|
+
return Storage3.disk(variant.disk).temporaryUrl(variant.path, options);
|
|
817
888
|
} catch {
|
|
818
889
|
return null;
|
|
819
890
|
}
|
|
820
891
|
}
|
|
821
892
|
async delete() {
|
|
822
|
-
|
|
823
|
-
try {
|
|
824
|
-
for (const variant of this.resolveStoredVariants()) {
|
|
825
|
-
const disk = Storage2.disk(variant.disk);
|
|
826
|
-
const contents = await disk.getBytes(variant.path);
|
|
827
|
-
await disk.delete(variant.path);
|
|
828
|
-
if (contents) {
|
|
829
|
-
deletedSnapshots.push({
|
|
830
|
-
...variant,
|
|
831
|
-
contents
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
await this.entity.delete();
|
|
836
|
-
registerDeletedVariantRollbackRestore(deletedSnapshots);
|
|
837
|
-
this.owner?.forgetRelation("media");
|
|
838
|
-
} catch (error) {
|
|
839
|
-
for (const snapshot of deletedSnapshots.reverse()) {
|
|
840
|
-
await Storage2.disk(snapshot.disk).put(snapshot.path, snapshot.contents).catch(() => void 0);
|
|
841
|
-
}
|
|
842
|
-
throw error;
|
|
843
|
-
}
|
|
893
|
+
await deleteMediaItems([this], this.owner);
|
|
844
894
|
}
|
|
845
895
|
async regenerate(conversions) {
|
|
846
896
|
await regenerateMediaEntityConversions({
|
|
@@ -848,18 +898,6 @@ var MediaItem = class {
|
|
|
848
898
|
owner: this.owner,
|
|
849
899
|
conversions
|
|
850
900
|
});
|
|
851
|
-
const definition = this.owner ? requireMediaDefinition(this.owner) : requireMediaDefinitionForMorphClass(String(this.entity.get("model_type")));
|
|
852
|
-
const queuedConversions = resolveQueuedConversionNames({
|
|
853
|
-
definition,
|
|
854
|
-
collectionName: String(this.entity.get("collection_name")),
|
|
855
|
-
requestedConversions: conversions
|
|
856
|
-
});
|
|
857
|
-
await dispatchQueuedMediaConversionsForModel({
|
|
858
|
-
mediaId: this.entity.get("id"),
|
|
859
|
-
conversionNames: queuedConversions
|
|
860
|
-
}, async () => {
|
|
861
|
-
await this.entity.refresh();
|
|
862
|
-
});
|
|
863
901
|
return this;
|
|
864
902
|
}
|
|
865
903
|
toJSON() {
|
|
@@ -882,24 +920,6 @@ var MediaItem = class {
|
|
|
882
920
|
disk: variant.disk ?? this.entity.get("conversions_disk") ?? this.entity.get("disk")
|
|
883
921
|
};
|
|
884
922
|
}
|
|
885
|
-
resolveStoredVariants() {
|
|
886
|
-
const variants = [{
|
|
887
|
-
path: this.entity.get("path"),
|
|
888
|
-
disk: this.entity.get("disk")
|
|
889
|
-
}];
|
|
890
|
-
const fallbackDisk = this.entity.get("conversions_disk") ?? this.entity.get("disk");
|
|
891
|
-
const conversions = this.entity.get("generated_conversions") ?? {};
|
|
892
|
-
for (const value of Object.values(conversions)) {
|
|
893
|
-
if (!value?.path) {
|
|
894
|
-
continue;
|
|
895
|
-
}
|
|
896
|
-
variants.push({
|
|
897
|
-
path: value.path,
|
|
898
|
-
disk: value.disk ?? fallbackDisk
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
return variants;
|
|
902
|
-
}
|
|
903
923
|
};
|
|
904
924
|
|
|
905
925
|
// src/model/adder.ts
|
|
@@ -931,12 +951,12 @@ var MediaAddValidationException = class extends Error {
|
|
|
931
951
|
}
|
|
932
952
|
};
|
|
933
953
|
function resolveImplicitDiskName() {
|
|
934
|
-
const defaultDisk =
|
|
954
|
+
const defaultDisk = Storage4.disk();
|
|
935
955
|
if (defaultDisk.visibility === "public") {
|
|
936
956
|
return defaultDisk.name;
|
|
937
957
|
}
|
|
938
958
|
try {
|
|
939
|
-
const publicDisk =
|
|
959
|
+
const publicDisk = Storage4.disk("public");
|
|
940
960
|
if (publicDisk.visibility === "public") {
|
|
941
961
|
return publicDisk.name;
|
|
942
962
|
}
|
|
@@ -1269,96 +1289,6 @@ function validateSource(collection2, source) {
|
|
|
1269
1289
|
}
|
|
1270
1290
|
}
|
|
1271
1291
|
}
|
|
1272
|
-
async function resolveNextOrderColumn(modelType, modelId, collectionName) {
|
|
1273
|
-
const max = await Media.query().where("model_type", modelType).where("model_id", modelId).where("collection_name", collectionName).max("order_column");
|
|
1274
|
-
return (max ?? 0) + 1;
|
|
1275
|
-
}
|
|
1276
|
-
async function deleteMediaItemsWithRollback(items) {
|
|
1277
|
-
const deletedSnapshots = [];
|
|
1278
|
-
try {
|
|
1279
|
-
for (const item of items) {
|
|
1280
|
-
const snapshot = await snapshotDeletedMediaItem(item);
|
|
1281
|
-
await item.delete();
|
|
1282
|
-
deletedSnapshots.push(snapshot);
|
|
1283
|
-
}
|
|
1284
|
-
} catch (error) {
|
|
1285
|
-
await restoreDeletedMediaSnapshots(deletedSnapshots);
|
|
1286
|
-
throw error;
|
|
1287
|
-
}
|
|
1288
|
-
return deletedSnapshots;
|
|
1289
|
-
}
|
|
1290
|
-
async function deleteOverflowItems(items, limit) {
|
|
1291
|
-
if (items.length <= limit) {
|
|
1292
|
-
return [];
|
|
1293
|
-
}
|
|
1294
|
-
return await deleteMediaItemsWithRollback(items.slice(0, items.length - limit));
|
|
1295
|
-
}
|
|
1296
|
-
async function cleanupGeneratedConversions(conversions, _fallbackDisk) {
|
|
1297
|
-
for (const conversion2 of Object.values(conversions)) {
|
|
1298
|
-
await Storage3.disk(conversion2.disk).delete(conversion2.path).catch(() => void 0);
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
function registerCreatedMediaRollbackCleanup(cleanup) {
|
|
1302
|
-
const active = connectionAsyncContext4.getActive()?.connection;
|
|
1303
|
-
if (!active || active.getScope().kind === "root") {
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
active.afterRollback(cleanup);
|
|
1307
|
-
}
|
|
1308
|
-
async function snapshotDeletedMediaItem(item) {
|
|
1309
|
-
const record = item.record;
|
|
1310
|
-
const fileTargets = [{
|
|
1311
|
-
disk: record.disk,
|
|
1312
|
-
path: record.path
|
|
1313
|
-
}];
|
|
1314
|
-
const fallbackDisk = record.conversions_disk ?? record.disk;
|
|
1315
|
-
for (const conversion2 of Object.values(record.generated_conversions ?? {})) {
|
|
1316
|
-
if (!conversion2?.path) {
|
|
1317
|
-
continue;
|
|
1318
|
-
}
|
|
1319
|
-
fileTargets.push({
|
|
1320
|
-
disk: conversion2.disk ?? fallbackDisk,
|
|
1321
|
-
path: conversion2.path
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1324
|
-
const files = (await Promise.all(fileTargets.map(async (file) => {
|
|
1325
|
-
const contents = await Storage3.disk(file.disk).getBytes(file.path);
|
|
1326
|
-
return contents ? {
|
|
1327
|
-
...file,
|
|
1328
|
-
contents
|
|
1329
|
-
} : null;
|
|
1330
|
-
}))).filter((file) => Boolean(file));
|
|
1331
|
-
return {
|
|
1332
|
-
record: {
|
|
1333
|
-
uuid: record.uuid,
|
|
1334
|
-
model_type: record.model_type,
|
|
1335
|
-
model_id: record.model_id,
|
|
1336
|
-
collection_name: record.collection_name,
|
|
1337
|
-
name: record.name,
|
|
1338
|
-
file_name: record.file_name,
|
|
1339
|
-
disk: record.disk,
|
|
1340
|
-
conversions_disk: record.conversions_disk,
|
|
1341
|
-
mime_type: record.mime_type,
|
|
1342
|
-
extension: record.extension,
|
|
1343
|
-
size: record.size,
|
|
1344
|
-
path: record.path,
|
|
1345
|
-
generated_conversions: record.generated_conversions,
|
|
1346
|
-
order_column: record.order_column
|
|
1347
|
-
},
|
|
1348
|
-
files
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
async function restoreDeletedMediaSnapshot(snapshot) {
|
|
1352
|
-
for (const file of snapshot.files) {
|
|
1353
|
-
await Storage3.disk(file.disk).put(file.path, file.contents);
|
|
1354
|
-
}
|
|
1355
|
-
await Media.create(snapshot.record);
|
|
1356
|
-
}
|
|
1357
|
-
async function restoreDeletedMediaSnapshots(snapshots) {
|
|
1358
|
-
for (const snapshot of snapshots.reverse()) {
|
|
1359
|
-
await restoreDeletedMediaSnapshot(snapshot).catch(() => void 0);
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
1292
|
var MediaAdder = class _MediaAdder {
|
|
1363
1293
|
constructor(entity, source, state = {}) {
|
|
1364
1294
|
this.entity = entity;
|
|
@@ -1420,93 +1350,93 @@ var MediaAdder = class _MediaAdder {
|
|
|
1420
1350
|
}
|
|
1421
1351
|
);
|
|
1422
1352
|
validateSource(collection2, source);
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
extension: source.extension,
|
|
1431
|
-
collection: collection2
|
|
1353
|
+
return await attachMedia({
|
|
1354
|
+
owner: this.entity,
|
|
1355
|
+
collectionName,
|
|
1356
|
+
collection: collection2,
|
|
1357
|
+
definition: mediaDefinition,
|
|
1358
|
+
source,
|
|
1359
|
+
disk: this.state.disk ?? collection2.disk ?? resolveImplicitDiskName()
|
|
1432
1360
|
});
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
async function attachMedia(options) {
|
|
1364
|
+
const { owner, collection: collection2, source, disk } = options;
|
|
1365
|
+
const ownerDefinition = owner.getRepository().definition;
|
|
1366
|
+
const ownerId = String(owner.get(ownerDefinition.primaryKey));
|
|
1367
|
+
const uuid = randomUUID();
|
|
1368
|
+
const conversionsDisk = collection2.conversionsDisk ?? disk;
|
|
1369
|
+
const path = getMediaPathGenerator().originalPath({ uuid, fileName: source.fileName, extension: source.extension, collection: collection2 });
|
|
1370
|
+
const obsolete = [];
|
|
1371
|
+
return await runMediaMutation({
|
|
1372
|
+
committedMessage: "[Holo Media] Attachment remains committed; post-commit cleanup or conversion dispatch failed.",
|
|
1373
|
+
afterRollback: () => {
|
|
1374
|
+
owner.forgetRelation("media");
|
|
1375
|
+
},
|
|
1376
|
+
operation: async (mutation) => {
|
|
1377
|
+
const existing = collection2.singleFile ? await owner.getMedia(options.collectionName) : [];
|
|
1378
|
+
await mutation.put(disk, path, source.contents);
|
|
1379
|
+
const generatedConversions = await generateStoredConversions({
|
|
1380
|
+
definition: options.definition,
|
|
1446
1381
|
collection: collection2,
|
|
1447
1382
|
conversionsDisk,
|
|
1448
|
-
source: {
|
|
1449
|
-
|
|
1450
|
-
fileName: source.fileName,
|
|
1451
|
-
extension: source.extension,
|
|
1452
|
-
mimeType: source.mimeType,
|
|
1453
|
-
size: source.size,
|
|
1454
|
-
contents: source.contents
|
|
1455
|
-
}
|
|
1383
|
+
source: { ...source, uuid },
|
|
1384
|
+
mutation
|
|
1456
1385
|
});
|
|
1386
|
+
const max = await Media.query().where("model_type", ownerDefinition.morphClass).where("model_id", ownerId).where("collection_name", options.collectionName).max("order_column");
|
|
1457
1387
|
const media = await Media.create({
|
|
1458
1388
|
uuid,
|
|
1459
1389
|
model_type: ownerDefinition.morphClass,
|
|
1460
|
-
model_id:
|
|
1461
|
-
collection_name: collectionName,
|
|
1390
|
+
model_id: ownerId,
|
|
1391
|
+
collection_name: options.collectionName,
|
|
1462
1392
|
name: source.name,
|
|
1463
1393
|
file_name: source.fileName,
|
|
1464
|
-
disk
|
|
1394
|
+
disk,
|
|
1465
1395
|
conversions_disk: conversionsDisk,
|
|
1466
1396
|
mime_type: source.mimeType ?? null,
|
|
1467
1397
|
extension: source.extension ?? null,
|
|
1468
1398
|
size: source.size,
|
|
1469
|
-
path
|
|
1399
|
+
path,
|
|
1470
1400
|
generated_conversions: generatedConversions,
|
|
1471
|
-
order_column:
|
|
1472
|
-
ownerDefinition.morphClass,
|
|
1473
|
-
String(ownerId),
|
|
1474
|
-
collectionName
|
|
1475
|
-
)
|
|
1401
|
+
order_column: (max ?? 0) + 1
|
|
1476
1402
|
});
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1403
|
+
for (const item of existing) await item.getEntity().delete();
|
|
1404
|
+
owner.forgetRelation("media");
|
|
1405
|
+
obsolete.push(...existing);
|
|
1480
1406
|
if (typeof collection2.onlyKeepLatest === "number") {
|
|
1481
|
-
const items = await
|
|
1482
|
-
|
|
1407
|
+
const items = await owner.getMedia(options.collectionName);
|
|
1408
|
+
const overflow = items.slice(0, Math.max(0, items.length - collection2.onlyKeepLatest));
|
|
1409
|
+
for (const item of overflow) await item.getEntity().delete();
|
|
1410
|
+
obsolete.push(...overflow);
|
|
1411
|
+
owner.forgetRelation("media");
|
|
1483
1412
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
await
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1413
|
+
return new MediaItem(media, owner);
|
|
1414
|
+
},
|
|
1415
|
+
afterCommit: [
|
|
1416
|
+
async (item) => {
|
|
1417
|
+
await removeObsoleteFiles(obsolete, [
|
|
1418
|
+
{ disk, path },
|
|
1419
|
+
...Object.values(item.record.generated_conversions).map((conversion2) => ({
|
|
1420
|
+
disk: conversion2.disk ?? conversionsDisk,
|
|
1421
|
+
path: conversion2.path
|
|
1422
|
+
}))
|
|
1423
|
+
]);
|
|
1424
|
+
},
|
|
1425
|
+
async (item) => {
|
|
1426
|
+
const media = item.getEntity();
|
|
1427
|
+
await dispatchQueuedMediaConversionsForModel({
|
|
1428
|
+
mediaId: media.get("id"),
|
|
1429
|
+
conversionNames: resolveQueuedConversionNames({ definition: options.definition, collectionName: collection2.name })
|
|
1430
|
+
}, async () => {
|
|
1431
|
+
await media.refresh();
|
|
1432
|
+
});
|
|
1503
1433
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1434
|
+
],
|
|
1435
|
+
afterEffects: () => {
|
|
1436
|
+
owner.forgetRelation("media");
|
|
1507
1437
|
}
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1510
1440
|
|
|
1511
1441
|
// src/model/entity.ts
|
|
1512
1442
|
async function clearEntityMedia(entity, collectionName) {
|
|
@@ -1515,9 +1445,7 @@ async function clearEntityMedia(entity, collectionName) {
|
|
|
1515
1445
|
return;
|
|
1516
1446
|
}
|
|
1517
1447
|
const rows = await query.get();
|
|
1518
|
-
|
|
1519
|
-
await new MediaItem(row, entity).delete();
|
|
1520
|
-
}
|
|
1448
|
+
await deleteMediaItems(rows.map((row) => new MediaItem(row, entity)), entity);
|
|
1521
1449
|
}
|
|
1522
1450
|
function getOwnerKey(entity) {
|
|
1523
1451
|
const ownerDefinition = entity.getRepository().definition;
|
|
@@ -1872,6 +1800,7 @@ export {
|
|
|
1872
1800
|
getMediaDefinition,
|
|
1873
1801
|
getMediaDefinitionForMorphClass,
|
|
1874
1802
|
getMediaPathGenerator,
|
|
1803
|
+
mediaRuntimeInternals,
|
|
1875
1804
|
normalizeCollectionDefinitions,
|
|
1876
1805
|
normalizeConversionDefinitions,
|
|
1877
1806
|
normalizeMediaConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/media",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"description": "Holo-JS Framework - media collections and conversions for Holo-JS models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
"test": "vitest --run"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@holo-js/config": "^0.3.
|
|
32
|
-
"@holo-js/db": "^0.3.
|
|
33
|
-
"@holo-js/queue": "^0.3.
|
|
34
|
-
"@holo-js/storage": "^0.3.
|
|
31
|
+
"@holo-js/config": "^0.3.18",
|
|
32
|
+
"@holo-js/db": "^0.3.18",
|
|
33
|
+
"@holo-js/queue": "^0.3.18",
|
|
34
|
+
"@holo-js/storage": "^0.3.18",
|
|
35
35
|
"sharp": "^0.35.4"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@holo-js/db-sqlite": "^0.3.
|
|
38
|
+
"@holo-js/db-sqlite": "^0.3.18",
|
|
39
39
|
"tsup": "^8.3.5",
|
|
40
40
|
"typescript": "^5.7.2",
|
|
41
41
|
"vitest": "^4.1.5"
|