Vertical Seeding Infrastructure
The vertical seeding system creates realistic, production-representative test data across all 11 Ngwenya Verticals with strict multi-tenant isolation. Each Malet is owned by a specific user or Organization, and every seeded entity is scoped to exactly one Malet.
NOTE
This page covers the vertical-specific seeding (11 malets, products, services). For the full 22-archetype platform seed including auth users, social graph, commerce, and enterprise features, see Seed Infrastructure.
NOTE
This infrastructure lives in scripts/db/verticals/ in the ngwenya-federation repository. All 11 verticals seed in ~1 second on an M-series Mac.
Architecture
Two-Plane Approach
The seed system mirrors the platform's own architectural split:
| Plane | Method | Used For |
|---|---|---|
| Control Plane | GraphQL mutations via Gateway | Malets, Products, Services โ entities with business-logic validators (slug generation, @Max() constraints, etc.) |
| Feature Plane | Direct MongoDB inserts via Mongoose | Events, Exhibitions, Galleries, Membership Plans โ experiences subgraph entities without public create mutations |
This split is necessary because the experiences subgraph exposes read-only queries for most entities. The seed scripts bypass the resolver layer to populate data that would normally be created by internal admin or backend workflows.
Shared Utilities
All 11 vertical scripts import from a common shared/ directory to eliminate duplication:
scripts/db/verticals/
โโโ shared/
โ โโโ config.ts # GATEWAY_URL, MONGODB_URI, POSTGRES_URI, TOKEN
โ โโโ gql-client.ts # GraphQL client with user impersonation
โ โโโ mongo.ts # Lazy Mongoose connection factory
โ โโโ ids.ts # oid(), daysAgo(), daysFromNow(), img()
โ โโโ users.ts # 22 users, 2 orgs, 11 malet definitions
โโโ seed-full.ts # Master orchestrator (all phases)
โโโ seed-all.ts # Verticals-only orchestrator (3 phases)
โโโ seed-users.ts # Auth users (Postgres)
โโโ seed-profiles.ts # User profiles (MongoDB)
โโโ seed-social.ts # Follows, wishlists, likes
โโโ seed-commerce.ts # Orders, carts, reviews
โโโ seed-community.ts # Discussions, comments, posts
โโโ seed-enterprise.ts # SAML, SCIM, roles, SIEM
โโโ clean.ts # Drops all seed collections
โโโ seed-{vertical}.ts # One per vertical (11 total)
Key Design: `ensureMalet()`
The shared gql-client.ts exposes an ensureMalet() function that handles idempotent Malet creation:
const { id, handle } = await ensureMalet({
handle: 'fashion-demo',
name: "Amara's Boutique",
tagline: 'Curated African fashion',
description: '...',
verticalType: 'fashion',
ownerType: 'USER',
ownerId: USER_SOLO_FASHION.id,
sectionTags: ['luxury', 'handmade'],
}, USER_SOLO_FASHION.id);
Internally, ensureMalet():
- Queries by handle โ if found, returns existing Malet (idempotent)
- Creates via
createOneMaletmutation โ only passes GQL-valid fields - Patches
ownerType/ownerIddirectly via MongoDB (not onMaletInputDTO)
User Simulation
The seed system simulates real-world multi-tenancy with 22 deterministic users, 2 Organizations, and both solo and team-based ownership patterns. See Seed Infrastructure for the full 22-archetype matrix.
Solo Malet Owners (Starter Tier)
Individual Malet Owners who own exactly one Malet:
| Handle | Name | Malet | Vertical |
|---|---|---|---|
amara-styles |
Amara Ndlovu | fashion-demo |
Fashion |
zuri-writes |
Zuri Okafor | author-demo |
Author |
lens-maestro |
Kagiso Molefe | photo-demo |
Photographer |
lerato-wellness |
Lerato Khumalo | serenity-spa-demo |
Wellness |
lindiwe-advisory |
Lindiwe Dlamini | advisory-demo |
Professional |
Organization-Owned Malets
Two Organizations with team structures and role-based access:
AfriArts Collective โ A creative agency operating 4 Malets:
| Handle | Name | Role | Responsibility |
|---|---|---|---|
thabo-mgmt |
Thabo Mthembu | OWNER | Overall management |
naledi-ops |
Naledi Kgosi | MEMBER | Kitchen operations |
sipho-trails |
Sipho Dlamini | MEMBER | Tour guide |
zanele-events |
Zanele Maseko | ADMIN | Team management |
bongani-viewer |
Bongani Nkosi | VIEWER | Read-only analytics |
Teams: "Events & Culture" (Thabo, Zanele), "Food & Tours" (Naledi, Sipho, Bongani)
Circuit Board Inc. โ Enterprise-tier consumer electronics with SAML/SCIM:
| Handle | Name | Role |
|---|---|---|
kwame-tech |
Kwame Asante | OWNER |
ayanda-dev |
Ayanda Moyo | MEMBER |
fatima-editor |
Fatima Hassan | MEMBER (AUTHOR_EDITOR) |
Vertical Breakdown
Malet Registry
| Handle | Name | Vertical | Owner |
|---|---|---|---|
fashion-demo |
Amara's Boutique | fashion | Amara Ndlovu (USER) |
author-demo |
Zuri's Tech Journal | author | Zuri Okafor (USER) |
photo-demo |
Lens & Light Studio | photographer | Kagiso Molefe (USER) |
serenity-spa-demo |
Serenity Wellness Spa | wellness | Lerato Khumalo (USER) |
advisory-demo |
Apex Financial Advisory | professional | Lindiwe Dlamini (USER) |
venue-demo |
The Grand Venue | entertainment | AfriArts Collective (ORG) |
gallery-demo |
The Zeitz Collection | culture | AfriArts Collective (ORG) |
tour-demo |
Ubuntu Safari Co. | tour | AfriArts Collective (ORG) |
restaurant-demo |
Umami Kitchen | restaurant | AfriArts Collective (ORG) |
tech-store-demo |
Circuit Board Tech | tech | Circuit Board Inc. (ORG) |
pixel-vault-demo |
Pixel Vault Games | arcade | Circuit Board Inc. (ORG) |
Seeded Entities Per Vertical
| Vertical | MongoDB Collections | Key Data |
|---|---|---|
| Fashion | products, lookbooks, productcollections | 4 products (size charts, variants), 1 lookbook (3 outfits), 3 collections |
| Entertainment | events, creditwallets, loyaltyconfigs, visitorloyalties | 3 events (tiered pricing), credit wallet, 4-tier loyalty + leaderboard |
| Professional | clientengagements, vaultdocuments | 4 engagements (lifecycle notes), 7 categorized documents |
| Wellness | wellnessprofiles, membershipplans, membershipsubscriptions | 1 profile, 4 plans (BronzeโPlatinum), 6 subscriptions |
| Culture | artistprofiles, exhibitions | 4 South African artists, 4 exhibitions (11 artworks) |
| Tech | products, productspecs, warrantyregistrations, warrantyclaims | 3 products (8 variants), 3 spec sheets, 3 warranties, 3 claims |
| Arcade | gameprofiles, digitalvaults | 4 games (system reqs), 6 key vaults (63 keys) |
| Tour | services | 3 multi-day itineraries (5โ7 days, detailed stops) |
| Restaurant | kitchenorders | 9 kitchen orders (SA cuisine, various statuses) |
| Photographer | galleries, proofingsessions | 4 galleries (110 photos), 3 proofing sessions |
| Author | subscriptions, blogs | 9 subscriptions, 7 blog posts (free + gated) |
Execution
Full Seed
# Seed all 11 verticals (3 phases, ~1 second)
make seed-verticals
# Full platform seed (all 22 archetypes, ~5 seconds)
make seed-full
# Or run the orchestrator directly
npx ts-node scripts/db/verticals/seed-all.ts
The orchestrator runs in dependency order:
Phase 1: Organizations โ AfriArts Collective, Circuit Board Inc.
Phase 2: Solo Malets โ fashion, author, photographer, wellness, professional
Phase 3: Org Malets โ entertainment, culture, tour, restaurant, tech, arcade
Individual Verticals
make seed-fashion # Amara's Boutique
make seed-entertainment # The Grand Venue
make seed-wellness # Serenity Wellness Spa
make seed-orgs # Organizations only
# ... etc (one target per vertical)
Clean Slate
# Drop 38 seed-related MongoDB collections
make seed-clean
# Or via the standalone script (no container CLI needed)
npx ts-node scripts/db/verticals/clean.ts
# Wipe + re-seed
make seed-clean && make seed-verticals
Environment Variables
| Variable | Default | Description |
|---|---|---|
GATEWAY_URL |
http://localhost:30000/graphql |
Hive Gateway endpoint |
MONGODB_URI |
mongodb://localhost:27017/ngwenya |
MongoDB connection string |
TOKEN |
Dev session token | Auth header for GQL mutations |
Frontend Catalog Integration
The catalog page ([malet]/catalog/+page.ts) uses a parallel fetch + graceful fallback strategy to display vertical-specific entities alongside core Products, Services, and Articles.
Data Loading Pattern
// Core data โ always fetched
const [products, services, blogs] = await Promise.all([
client.request(GET_MALET_PRODUCTS, { maletId }),
client.request(GET_MALET_SERVICES, { maletId }),
client.request(GET_MALET_BLOGS, { maletId }),
]);
// Vertical-specific โ wrapped in safeFetch()
const [events, exhibitions, plans] = await Promise.all([
safeFetch(client.request(GET_UPCOMING_EVENTS, { maletId }), { upcomingEvents: [] }),
safeFetch(client.request(GET_ACTIVE_EXHIBITIONS, { maletId }), { activeExhibitions: [] }),
safeFetch(client.request(GET_MEMBERSHIP_PLANS, { maletId }), { membershipPlans: [] }),
]);
`safeFetch()` Pattern
The safeFetch() wrapper ensures the catalog page never crashes when a subgraph query doesn't match the current Malet's vertical. For example, a Fashion Malet has no Events โ the upcomingEvents query returns [] via graceful fallback rather than throwing.
async function safeFetch<T>(promise: Promise<T>, fallback: T): Promise<T> {
try { return await promise; } catch { return fallback; }
}
Dynamic Sections
The catalog template conditionally renders sections based on what data is available:
| Section | Query | Renders When |
|---|---|---|
| Products | GET_MALET_PRODUCTS |
products.length > 0 |
| Services | GET_MALET_SERVICES |
services.length > 0 |
| Events | GET_UPCOMING_EVENTS |
events.length > 0 |
| Exhibitions | GET_ACTIVE_EXHIBITIONS |
exhibitions.length > 0 |
| Membership Plans | GET_MEMBERSHIP_PLANS |
membershipPlans.length > 0 |
| Articles | GET_MALET_BLOGS |
blogs.length > 0 |
Idempotency
All scripts are safe to re-run:
- Malet creation:
ensureMalet()checks for existing handle first - Product creation:
ensureProduct()checks for existing slug+maletId before creating (idempotent) - MongoDB inserts: wrapped in try/catch that skips
E11000duplicate key errors - Deterministic IDs: User UUIDs are hardcoded in
shared/users.ts - Meilisearch reindex:
seed-full.tsPhase 10 automatically reindexes (non-fatal if Meilisearch is unavailable)
For a completely clean state, run make seed-clean before seeding.
Common Pitfalls
| Issue | Cause | Fix |
|---|---|---|
Field "ownerType" not defined by MaletInput |
ownerType/ownerId not on GQL DTO |
ensureMalet() patches via MongoDB after creation |
| Tour services fail with "Bad Request" | Duration exceeds @Max(1440) validator |
Multi-day tours use direct MongoDB insert |
| SizeChart entries fail Float validation | Measurements must be numeric (82) not strings ("80-84cm") |
Use midpoint values in cm |
| Membership plans show no price | Backend entity uses monthlyPrice, not pricePerMonth |
Match field names exactly |
Related
- Seed Infrastructure โ Full 22-archetype platform seeding, social graph, commerce, enterprise
- Entertainment & Experiences โ Event ticketing, credit wallets, and loyalty systems
- Fashion Vertical โ Size charts, variant grids, and lookbooks
- Wellness & Beauty โ Membership plans, wellness profiles, and subscriptions
- Local Development Environment โ Service startup and legacy seed commands
- Teams & Sub-Groups โ Organization structure and role-based access