生成媒體不應只靠 URL 管理,應另設資產清單
A URL is not an asset record: keep a manifest beside generated media
文章提出一種應用程式資料模型,將生成媒體的資產身份、生成配方和交付位置分開記錄,避免把交付連結變更誤當成新生成結果。文中的 TypeScript 僅為示例,並非供應商 SDK 或既有正式資料結構;文章建議用虛構回應測試配方修訂、未知到期時間、存檔失敗及跨專案私有配方存取等情況。
A media-generation prototype can get surprisingly far with an array of output URLs. Then someone asks which reference image produced a clip, whether two results used the same settings, or why a saved link no longer opens. The array cannot answer those questions.
A useful next step is a small asset manifest. Give the result an application-owned identity, and store its recipe separately from its current delivery address.
This article proposes an application data model. The TypeScript is illustrative, not a provider SDK or a description of an existing production schema. This tutorial was drafted by an AI agent and reviewed for accuracy by PopNow Team before publication. The team confirmed that it reflects knowledge they are familiar with. No remote generation or benchmark was run for it.
Separate identity, recipe, and delivery
These three concepts change for different reasons:
- Identity: the asset your application and its users refer to.
- Recipe: the inputs and settings that explain one generation attempt.
- Delivery: where an output can currently be obtained.
A refreshed signed URL does not necessarily mean a new creative result. A changed prompt does mean a new recipe. The application should not infer either fact from string equality between URLs.
Here is one possible TypeScript model:
type Recipe = Readonly<{
id: string;
modelRef: string;
promptRef: string;
referenceAssetIds: readonly string[];
settings: Readonly<Record<string, string | number | boolean>>;
}>;
type Delivery =
| {
kind: "remote";
url: string;
observedAt: string;
expiresAt?: string;
}
| {
kind: "stored";
objectKey: string;
contentType: string;
checksum?: string;
};
type MediaAsset = {
id: string;
projectId: string;
recipeId: string;
providerTaskRef?: string;
createdAt: string;
delivery: Delivery;
};
These names belong to the application. An adapter translates a provider's actual response into this representation; the type definitions do not imply that any particular API returns these fields.
promptRef is a reference to an access-controlled record, not a reason to copy prompts into analytics events. Similarly, a storage object key belongs in the manifest; public access can be decided separately when someone asks to view the asset.
Snapshot the recipe before starting work
Suppose a user selects a reference image and adjusts the duration. Save that approved configuration as a recipe before submitting the generation request. Associate the returned task reference with that attempt when it becomes available.
Do not let a later edit mutate the historical recipe. If a user revises the prompt, create another recipe and associate the next attempt with it. Otherwise an old clip will appear to have been generated from today's settings.
The Readonly types above help express intent to TypeScript callers. They do not freeze objects at runtime or enforce database immutability. A production implementation still needs its own write rules, versioning strategy, and authorization checks.
Keep delivery updates narrow
When a provider supplies a replacement delivery link for an existing result, the adapter may update the delivery information associated with that asset. It should not overwrite the recipe or invent a new creative revision merely because the address changed.
Conversely, do not assume that any two URLs pointing to apparently similar images identify the same output. Deduplication needs evidence appropriate to the application. A provider's stable output identifier can be useful; a checksum of downloaded bytes answers a narrower question about byte identity.
An expiry field should be filled only when its meaning is documented or otherwise known. An absent expiresAt means "unknown in this record," not "available forever." Decide what the UI should do when a remote link is unavailable, instead of hiding that state behind a permanent thumbnail.
If your application copies an output into its own storage, record that completed storage operation separately. A task reaching a completed state is not proof that your archive copy succeeded.
Build comparisons from the manifest
A comparison view can now show a thumbnail with its model reference, recipe, and selected settings. It can keep a selected result tied to the exact attempt a reviewer approved.
For a fictional product-photo comparison, a reviewer might record:
asset-101 -> recipe-31 -> keep
asset-102 -> recipe-32 -> revise: label changed
asset-103 -> recipe-33 -> reject: wrong object shape
The review belongs to the asset and its recorded recipe. Replacing a thumbnail in a grid should not silently move that decision to a different result.
This also makes exports clearer. A project can export selected assets together with a manifest that explains their provenance, without pretending that the manifest itself grants rights to the input references or generated content.
Check the boundaries with local fixtures
You do not need a paid generation call to exercise this data model. Construct fictional provider responses and check that:
- Updating a delivery link preserves the asset and recipe identities.
- A deliberate input revision creates a new recipe.
- Unknown expiry remains unknown.
- A failed archive copy does not mark the output as stored.
- A project cannot resolve another project's private recipe through an asset reference.
These are suggested application tests, not claims that a remote service has passed them. The point is to make the relationships explicit enough that a test can describe the behavior you intend.
An asset manifest is a modest addition to a prototype. Its value is that "which result is this?", "how was it requested?", and "where can I retrieve it?" become separate questions with separate answers.
來源:DEV Community · dev.to