Skip to content

Domain Model

@nx/search owns no PostgreSQL tables. Its "domain" is the set of Typesense collections — denormalized read projections of source tables in the public, pricing, and inventory schemas. Schema definitions live in src/configurations/<entity>.collection.ts (shared shapes in schema-fragments.ts).

1. Full ERD

CDC source tables (left) projected into Typesense collections (right). Solid = direct doc source; dashed = cascade-only.

2. Entities (Collections)

One block per collection. Each is a flat-ish Typesense document; i18n { en, vi } is flattened to field.en / field.vi. queryBy lists the searchable text fields; facetable fields are filterable.

organizers

PropertyValue
Sourcepublic.Organizer (direct)
Configsrc/configurations/organizer.collection.ts
Doc idOrganizer.id
queryByname.en, name.vi, description.en, description.vi, slug, identifier
Relationsparent (organizers, hydration)
Embeddingoptional

merchants

PropertyValue
Sourcepublic.Merchant (direct)
Configsrc/configurations/merchant.collection.ts
Doc idMerchant.id
queryByname.en, name.vi, description.en, description.vi, slug, identifier
Relationsorganizer (native), parent (merchants, hydration)

categories

PropertyValue
Sourcepublic.Category (direct)
Configsrc/configurations/category.collection.ts
Doc idCategory.id
queryByname.en, name.vi, description.en, description.vi
Relationsmerchant (native)

devices

PropertyValue
Sourcepublic.Device (direct)
Configsrc/configurations/device.collection.ts
Doc idDevice.id
queryByname.*, identifier, code
Relationsorganizer (native), merchant (native)

sale-channels

PropertyValue
Sourcepublic.SaleChannel (direct)
Configsrc/configurations/sale-channel.collection.ts
Doc idSaleChannel.id
queryByname.en, name.vi, slug, identifier
Relationsmerchant (native), parent (sale-channels, hydration)

products

PropertyValue
Sourcepublic.Product + public.ProductInfo (i18n) — both direct (info is partial update)
CascadeProductCategorycategoryIds[]
Configsrc/configurations/product.collection.ts (+ product-info-mapper)
Doc idProduct.id
queryByidentifier, slug, info.name.{en,vi}, info.description.{en,vi}, merchant_name.{en,vi}, organizer_name.{en,vi}, sale_channel_names
Facetsstatus, merchantId (ref merchants.id), categoryIds[], parentId, denormalized parent names
Relationsmerchant (native), parent (products, hydration)
Embeddingembedding field from [identifier, slug]

product-variants

PropertyValue
Sourcepublic.ProductVariant (direct) + ProductVariantInfo
CascadeProductCategory (categoryIds), MetaLink (metaLinks), FareSet/Fare (fareSet, defaultPrice), ProductBundler (comboItems)
Configsrc/configurations/product-variant.collection.ts
Doc idProductVariant.id
Relationsproduct (native), merchant (native)

inventories

PropertyValue
Sourceinventory.InventoryStock (direct, doc grain)
CascadeInventoryItem, InventoryLocation, InventoryIdentifier, Material
Configsrc/configurations/inventory.collection.ts
Doc idInventoryStock.id (== inventoryStockId)
queryByitem.name.{en,vi}, location.name.{en,vi}, identifiers.value, stock.lotNumber, stock.serialNumber, item.slug, item.description.{en,vi}
Structurenested item / location / stock groups; identifiers[] (scheme-agnostic); flags[] (derived booleans)
Notesflags.isExpired / isExpiringSoon go stale — refreshed by periodic reindex or query-time range

3. Cross-entity Invariants

InvariantEnforcement
Document id always equals the source row idMapper extractDocumentId / idField
Soft-deleted source row ⇒ document absentMapper returns nullCDCService deletes
Out-of-order CDC events never overwrite newer stateLSN guard (source_lsn) + deleted_at compare
products.categoryIds reflects all ProductCategory rowsCascade fan-out + enrichment (not the row mapper)
inventories doc is one-per-InventoryStock, item/location denormalizedCascade from InventoryItem/InventoryLocation
Typesense reference fields must be top-levelSchema places merchantId, inventoryItemId, etc. at top level

4. Soft-delete Behavior

BehaviorDetail
Source of truthPostgreSQL deletedAt (owned by source services)
Search effectMapper detects deletedAt != null → emits delete to Typesense
Read defaultSoft-deleted rows have no document, so they never appear in results
Hard-deleteA Debezium d op also deletes the document
RestoreA subsequent non-deleted CDC event re-upserts the document

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.