entity

Project Collection

Designer-created collection or favorites list of products. Aggregate root for add, remove, select, and portal publish.

Entity(Aggregate Root)Status: Implemented in api-v1Aggregate Root

Purpose

A user product collection is a designer-curated list of products for a project or design workflow. Short domain name: Collection. Catalog identity lives in product-marketplace; this aggregate holds ProductReference values on its items.

Manufacturer or vendor product lines are VendorProductCollection, not this aggregate.

Products enter a project from Product Marketplace Add to Project (or Design Add Item to Project). That path finds or creates the default Project Collection (collectionKind=default, name {projectName}-default) and upserts Project Product Variant rows (ADR-0083). Graph assignment is a separate PDG command. Extra lists on the same project add membership on the existing item (add-items). Do not create a second item for a variant already on the project.

Invariants

  • name is required and non-empty after trim.
  • coverImageUrl, when set, must be an absolute http or https URL.
  • collectionKind is default or curated. create() writes curated. createDefault() writes default, requires projectId, and names the collection {projectName}-default.
  • Default collections cannot be renamed (DefaultCollectionCannotBeRenamedError).
  • Copy Collection is Airtable-only and always writes collection_kind=curated so a default source cannot produce a second default.
  • Mutations (addItem, addProductReferences, removeItems, select, publishToPortal, unpublishFromPortal, rename) require status active.
  • Two active items cannot share the same product reference (DuplicateProjectCollectionItemError). addProductReferences counts duplicates and continues; addItem fails.
  • removeItems marks matching active items deleted. Persistence unlinks deleted items from the parent collection rather than always destroying the Airtable row.
  • select(true|false) sets the collection script flag and copies that value onto every active item.
  • unpublishFromPortal is a no-op when already draft (keeps lastUnpublishedAt).

Domain Events

None. The module glossary records that earlier event types were removed because no dispatcher consumed them and use cases already return application results. Do not invent events.