Product Variant Catalog Changed
A persisted product variant's catalog display data changed. Dispatched after scrape ingest, from-draft persist, model identity update / batch patch, or catalog reconcile.
Purpose
Integration event for downstream catalog consumers (design-management ProjectProductVariant projections). Payload is primitive IDs only (no domain objects).
Thin payload + read-back (hex §7 deviation, D9). Handlers re-read the current PM snapshot via ForReadingProductVariantSnapshots. Field values are not on the event: for AT-C1 the only trustworthy state is the current Airtable record, and the snapshot is 40+ fields.
Related-product (accessory) links use RelatedProductAdded, not this event — the variant aggregate did not change.
Payload
Envelope is { name, occurredAt, payload } (apps/api-v1/src/modules/shared/domain/events/DomainEvent.ts). One event per distinct productVariantId; do not collapse to a single productId.
After scrape ingest persist success, assembler-local ids (product:sku-1) remap onto Airtable RECORD_ID() values from persist productVariantIds / productModelId when that list is non-empty. Refresh/reload already uses rec or stored domain_id. Design-management readByIds matches RECORD_ID() or domain_id. Dispatch is unique per variant id (model + variant both changing does not send 2N events).
| Field | Type | Meaning |
|---|---|---|
productVariantId | string | Variant that changed (Airtable rec after scrape ingest remap, or stored domain_id) |
productModelId | string | null | Owning product model when known |
Raised by
- Ingestion —
IngestScrapedCatalogUseCaseafter scrape persist success (HTTP create-from-url, refresh, reload, scrape-request, live CLIpersistRawProduct). Events come frompullDomainEvents()onProductModel/ProductVariant, then remap/unique before dispatch. Parameter-graph writes on those persist paths ride the same drain. - From-draft —
RunCreateProductModelFromDraftWorkflowUseCaseafterpersistDraftsuccess. - Model identity update / batch patch —
UpdateProductModelUseCaseandBatchPatchProductModelsUseCaseafterrename/assignVendor/assignObjectClass(and reconcile-style identity mutations) record events on the model and drain after save. - Catalog reconcile —
RequestCatalogReconcileUseCase(POST …/product-models/:id/reconcile,POST …/product-variants/:id/reconcile; AT-C1request_reconcile.js). Recompute only; writes nothing to PM.
HTTP operations that can lead to dispatch (OpenAPI x-domain.domainEvents): create-from-url, from-draft, product-scrape, refresh, reload, reconcile. runBulkProductScraper does not dispatch on enqueue (domainEvents: []); later per-URL ingest may.
Not dispatched when persist writes no variant row, persist skips/fails, or aggregate events are empty. Identity update does dispatch.
Image-record create, unlink, or destroy is not this event. That is ProductVariantImagesChanged. Snapshot imageUrls (Airtable attachments copied onto a Design row) may still refresh when this event fires for other display-field changes; Supabase publish and delete do not ride this event.
Consumers
RefreshProjectProductVariantsOnCatalogChangedHandler in design-management. Refreshes existing project product rows only (never creates).
Related
- RelatedProductAdded
- ProductVariantImagesChanged
- Create Product From URL
- Refresh and Reload Product Scrape
- Code:
apps/api-v1/src/modules/product-marketplace/core/domain/product-variant/ProductVariant.events.ts