Domain Model
@nx/assetowns no schema. The single entity it reads/writes —MetaLink— is defined in@nx/coreso other packages (commerce, ledger) can reference it without circular deps.
1. Full ERD
Relations are soft polymorphic —
principalIdis a plaintextcolumn resolved against whichever table matchesprincipalType. There are no DB foreign keys.
2. Entities
MetaLink
| Property | Value |
|---|---|
| Table | public.MetaLink |
| Source | packages/core/src/models/schemas/public/meta-link/schema.ts |
| Soft-delete | no — uses generateTzColumnDefs() timestamps; asset deletes via hard deleteAll |
| Owner ID column | principalId (polymorphic, optional) |
Fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | text | ✓ | Snowflake | Primary key |
bucketName | text | ✓ | — | S3/Minio bucket |
objectName | text | ✓ | — | Object key within the bucket |
link | text | ✓ | — | Relative access path (/assets/objects/{name}) |
mimetype | text | ✓ | — | Content type from stat metadata |
size | integer | ✓ | — | Object size in bytes |
etag | text | — | S3 etag | |
variant | text | — | Caller-supplied variant tag (e.g. thumbnail role) | |
metadata | jsonb | — | Record<string, any> — raw S3 stat metadata | |
storageType | text | ✓ | — | Always s3 today |
isSynced | boolean | ✓ | false | True once the MetaLink mirrors the stored object (set on upload) |
principalType | text | — | Owning entity kind — see enum | |
principalId | text | — | Owning entity ID | |
createdAt / updatedAt | timestamp | ✓ | now() | From generateTzColumnDefs() |
Principal type enum (MetaLinkPrincipalTypes):
| Value | Description |
|---|---|
Product | Product image / document |
ProductVariant | Variant-specific media |
Organizer | Organizer branding |
Ledger | Generated PDF/XLSX (ledger output) |
Category | Category artwork |
The
MetaLink.relations()definition in core also declaresmerchantanduserrelations (matched onprincipalId), though those values are not in theMetaLinkPrincipalTypesvalidation set.
Indexes & constraints:
| Name | Columns | Type |
|---|---|---|
PK_MetaLink | id | Primary key |
IDX_MetaLink_bucket_name | bucketName | Btree |
IDX_MetaLink_object_name | objectName | Btree |
IDX_MetaLink_storage_type | storageType | Btree |
IDX_MetaLink_is_synced | isSynced | Btree |
IDX_MetaLink_principal_type_principal_id | principalType, principalId | Btree (composite) |
IDX_MetaLink_principal_id | principalId | Btree |
Relations:
| Field | Cardinality | References |
|---|---|---|
principalId (type Product) | M:1 | Product.id |
principalId (type ProductVariant) | M:1 | ProductVariant.id |
principalId (type Organizer) | M:1 | Organizer.id |
principalId (type Ledger) | M:1 | Ledger.id |
principalId (type Category) | M:1 | Category.id |
3. Cross-entity Invariants
| Invariant | Enforcement |
|---|---|
A MetaLink mirrors exactly one stored object (bucketName + objectName) | Created in the same handler as helper.upload |
| Deleting an object removes its MetaLink rows | AssetController deleteAll({ where: { bucketName, objectName } }) after removeObject (best-effort, async) |
storageType = 's3' | Hardcoded at create — no other backend is live (see ADR-0001) |
Polymorphic owner resolves only when principalType ∈ MetaLinkPrincipalTypes | MetaLinkPrincipalTypes.isValid() (validation helper, not a DB constraint) |
4. Soft-delete Behavior
| Behavior | Detail |
|---|---|
| Read default | All rows visible — MetaLink has no deletedAt column |
| Hard-delete | Yes — deleteAll on object delete; /meta-links CRUD deleteById/deleteBy |
| Restore | Not supported |