fix(images): bound external runtime processing
This commit is contained in:
@@ -177,10 +177,11 @@ export async function processSignageImage(bytes: Uint8Array): Promise<SignagePro
|
||||
height: metadata.height as number,
|
||||
width: metadata.width as number,
|
||||
};
|
||||
const [display, thumbnail] = await Promise.all([
|
||||
renderRendition(bytes, SIGNAGE_IMAGE_DISPLAY_RENDITION),
|
||||
renderRendition(bytes, SIGNAGE_IMAGE_THUMBNAIL_RENDITION),
|
||||
]);
|
||||
// Keep peak native CPU and memory bounded inside the shared API process. Upload concurrency is
|
||||
// already bounded at the module UI; running both renditions for every upload in parallel would
|
||||
// multiply that concurrency again and can starve the database and storage clients on a small host.
|
||||
const display = await renderRendition(bytes, SIGNAGE_IMAGE_DISPLAY_RENDITION);
|
||||
const thumbnail = await renderRendition(bytes, SIGNAGE_IMAGE_THUMBNAIL_RENDITION);
|
||||
|
||||
return {
|
||||
byteSize: bytes.byteLength,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import {
|
||||
MODULE_STORAGE_ERROR_CODES,
|
||||
type ModuleStorage,
|
||||
type ModuleStorageBucket,
|
||||
ModuleStorageError,
|
||||
type ModuleStorageErrorCode,
|
||||
} from "@ekklesia/module-sdk";
|
||||
import { SignageError } from "../errors.js";
|
||||
import { type SignageStore, SignageStoreConflict, type StoredImage } from "../store/types.js";
|
||||
@@ -17,6 +18,20 @@ export function nativeImageBucket(storage: ModuleStorage): ModuleStorageBucket {
|
||||
return storage.bucket(SIGNAGE_IMAGE_BUCKET);
|
||||
}
|
||||
|
||||
/** Host errors cross the installed-package boundary from a different copy of the SDK, so class
|
||||
* identity is not part of the contract (ADR 0049). Recognize only the SDK's closed, sanitized
|
||||
* storage-error shape; provider messages and causes remain inaccessible. */
|
||||
function storageErrorCode(error: unknown): ModuleStorageErrorCode | undefined {
|
||||
if (!(error instanceof Error) || error.name !== "ModuleStorageError") {
|
||||
return undefined;
|
||||
}
|
||||
const code = (error as { readonly code?: unknown }).code;
|
||||
return typeof code === "string" &&
|
||||
(MODULE_STORAGE_ERROR_CODES as readonly string[]).includes(code)
|
||||
? (code as ModuleStorageErrorCode)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object keys are scoped to the asset they belong to.
|
||||
*
|
||||
@@ -41,7 +56,7 @@ export function renditionObjectKey(assetId: string, kind: string): string {
|
||||
* not a caller's business.
|
||||
*/
|
||||
function storageFailure(error: unknown): SignageError {
|
||||
if (error instanceof ModuleStorageError && error.code === "STORAGE_OBJECT_NOT_FOUND") {
|
||||
if (storageErrorCode(error) === "STORAGE_OBJECT_NOT_FOUND") {
|
||||
return new SignageError("SIGNAGE_NOT_FOUND");
|
||||
}
|
||||
return new SignageError("SIGNAGE_STORAGE_UNAVAILABLE");
|
||||
@@ -150,7 +165,7 @@ export async function ingestNativeImage(
|
||||
if (error instanceof SignageError) {
|
||||
throw error;
|
||||
}
|
||||
if (error instanceof ModuleStorageError) {
|
||||
if (storageErrorCode(error) !== undefined) {
|
||||
throw storageFailure(error);
|
||||
}
|
||||
throw new SignageError("SIGNAGE_OPERATION_FAILED");
|
||||
|
||||
Reference in New Issue
Block a user