3.1 KiB
3.1 KiB
F-138 — Architect note (design)
What
F-138 — close the GET /pricing/variants/:id 404 race by seeding a default pricing_variant_prices row immediately after CreateProductVariant persists a variant. Pricing owns its table (F-153 precedent for cross-module table read; for cross-module writes the repo uses service injection — same pattern cart already uses on pricing in build-app.ts).
Why
variants.create only inserts catalog_product_variants; it never inserts pricing_variant_prices. Three call sites create variants (product autovariant, lazy GET, explicit POST) — all 404 on the pricing GET until an admin prices them. Customer/cart flows that read a price right after variant creation break.
Approach
PricingRepository+PricingServicegainseedVariantPrice(variantId): Promise<void>.PgPricingRepository.seedVariantPrice:INSERT pricing_variant_prices(variant_id, net_unit_amount_cents, offer_cents, cost_cents, vat_rate) VALUES ($1, 0, NULL, NULL, 'general') ON CONFLICT (variant_id) DO NOTHING— same column set assetVariantPrice(currency/created_at/updated_atcome from DDL defaults). Idempotent + no injection (variantIdis a UUID produced byvariants.create).CreateProductVariantctor +=pricing: PricingService(type-only import of pricing public index — R1 legal); aftervariants.createreturns, best-effortawait this.pricing.seedVariantPrice(variant.id)(try/catch: variant already persisted; seed failure must not fail creation).catalog.routes.ts:CatalogRoutesDeps += pricing; wirenew CreateProductVariant(repository, variants, pricing). Single constructor → all 3 call sites seeded.build-app.ts: hoistconst pricing = createPricingService(deps.pool)above theregisterCatalogRoutesblock so it can be passed in; pricing routes + cart keep using it (unchanged).createPricingServicealready imported.
Alternatives considered
- (a) Raw SQL insert from
CreateProductVariantviadeps.pool— REJECTED: catalog app layer shouldn't own pricing table writes (layering; reviewer would flag). Service injection keeps ownership clean. - (b) Seed in
PgProductVariantRepository.create— REJECTED: couples catalog infra to pricing schema; breaks single-responsibility. - (c)
PricingServiceinjection (CHOSEN) — matches the establishedcart→pricingcross-module write pattern; testable with a fake.
Risks
- build-app ordering: hoisting
pricingconst is a pure construction reorder — safe (deps.poolavailable, createPricingService has no side effects). - Seed failure: made best-effort so variant creation never regresses (AC3).
- tsc
strict+noUncheckedIndexedAccess: not relevant here (no array indexing in new code).
Tests
catalog/tests/variant-use-cases.test.ts(unit, runnable): fakes for ProductRepository, ProductVariantRepository, PricingService — asserts seed-called, no-seed-when-product-missing, seed-failure-doesn't-break-creation.catalog.itest.tsAC itest (skipIf !hasDb): POST variant → GET/pricing/variants/:id200 + defaults.- No migration (variant_id UNIQUE/PK already).