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. That action calls Design’s add-variants write port, which finds or creates the default Project Collection (collectionKind=default, name {projectName}-default) and upserts Project Product Variant rows (ADR-0083). There is no Design-owned Add to Project. 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.

Removing an item from a curated collection re-homes it onto the default collection and does not destroy the project_product_variants row. Destroying the item on the project (POST /project-collections/delete with children, which runs releaseIfUnreferenced) emits ProjectProductVariantReleased from that aggregate.

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.
  • INV-7: a collection item is created only as a member of this collection. addItem and addProductReferences stamp this collection’s id. rehydrate stamps a missing id and rejects a mismatched one.
  • 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

This aggregate does not emit. Collection commands return application results. Membership that creates or destroys a ProjectProductVariant is recorded on that aggregate, not here.