Getting Started
Core Architecture
Link Engine
Analytics & Attribution
Partners & Affiliates
Third-Party Integrations
Identity & Security
Automation & Messaging
Developer Tools
The following files were used as context for generating this wiki page:
The quick start guide establishes the local development environment and database infrastructure required to run the Dub monorepo stack. It covers workspace dependency management, Turborepo build orchestration, Docker Compose service configuration, and various database initialization and seeding workflows. Developers can provision core fixtures, generate mock partner accounts, commission records, and referral application events, configure third-party integration secrets, and populate large-volume datasets for search indexing, vector embedding, and performance benchmarking. Sources: apps/web/docker-compose.yml:1-46, apps/web/scripts/dev/seed-100k-partners.ts:1-27, apps/web/scripts/dev/seed.ts:1-241, apps/web/scripts/dev/benchmark-partner-search.ts:1-26, turbo.json:1-20
The Dub repository operates as a monorepo utilizing Turborepo build orchestration to manage package pipelines and task dependencies. Workspace package management relies on pnpm workspaces, linking internal packages such as @dub/email, @dub/embed-react, @dub/tailwind-config, @dub/ui, and @dub/utils directly into the web application workspace. Turborepo coordinates pipeline execution through turbo.json, enforcing build and test dependencies across workspace packages while maintaining caching and persistent service processes. Sources: apps/web/package.json:1-184, turbo.json:1-20
Turborepo reads configuration from turbo.json to schedule execution tasks across the workspace. The build pipeline defines explicit task dependencies and output boundaries, treating global environment files (**/.env) as global dependencies that invalidate cache states when modified. Sources: turbo.json:1-20
Sources: turbo.json:1-20
The apps/web package defines scripts in package.json for development execution, database schema generation, testing, and OpenAPI spec generation. These scripts integrate Prisma client generation workflows directly prior to compilation, test execution, or server startup. Sources: apps/web/package.json:5-20
Sources: apps/web/package.json:5-20
Note
Workspace packages such as @dub/email, @dub/embed-react, @dub/tailwind-config, @dub/ui, and @dub/utils use workspace protocol specifiers (workspace:*), forcing pnpm to link local workspace directories rather than fetching from registry endpoints during monorepo builds. Sources: apps/web/package.json:33-37
The monorepo defines baseline constants for local execution fallbacks, including geographical coordinates and IP address defaults used during local analytics ingestion or testing. Sources: packages/utils/src/constants/localhost.ts:1-10
export const LOCALHOST_GEO_DATA = {
continent: "NA",
country: "US",
city: "San Francisco",
region: "CA",
latitude: "37.7695",
longitude: "-122.385",
};
export const LOCALHOST_IP = "63.141.57.109";The local development stack relies on Docker Compose to provision backing services, including a MySQL database configured for PlanetScale compatibility and a local mail server. Sources: apps/web/docker-compose.yml:1-46
The apps/web/docker-compose.yml configuration specifies version 3.8 and defines three primary services alongside a persistent volume for database storage. Sources: apps/web/docker-compose.yml:1-46
Sources: apps/web/docker-compose.yml:5-43
Warning
The Docker Compose configuration is explicitly intended for local development only and must not be utilized in production environments. Sources: apps/web/docker-compose.yml:1-2
The database seeding workflow initializes the primary storage layer, loads structured test fixtures, and establishes a default entity graph containing workspaces, user roles, domains, email domains, folders, rewards, partner groups, programs, and partner accounts. Sources: apps/web/scripts/dev/seed.ts:1-241, apps/web/scripts/dev/data.json:1-181
The seeding utility reads static fixture payloads defined in data.json and maps them into strongly typed Prisma create operations. The root structure consists of distinct entity arrays and singletons. Sources: apps/web/scripts/dev/seed.ts:121-135, apps/web/scripts/dev/data.json:1-181
The execution pipeline reads the local fixture file and invokes sequential database creation handlers to construct relational dependencies in proper foreign key order. Sources: apps/web/scripts/dev/seed.ts:137-241
parseJSON() — Reads and parses apps/web/scripts/dev/data.json into memory as a SeedData structure. Sources: apps/web/scripts/dev/seed.ts:137-141createWorkspace() — Inserts the root tenant record into prisma.project. Sources: apps/web/scripts/dev/seed.ts:144-152createUsers() — Hashes the default password (password), executes prisma.user.createMany(), assigns workspace memberships via prisma.projectUsers.createMany(), queries back generated relation identifiers, and initializes prisma.notificationPreference.createMany(). Sources: apps/web/scripts/dev/seed.ts:155-210createDomains() — Populates verified routing domains through prisma.domain.createMany(). Sources: apps/web/scripts/dev/seed.ts:213-231createEmailDomains() — Commits email domain verification states to the database. Sources: apps/web/scripts/dev/seed.ts:233-241Warning
Because createMany batch operations do not return auto-incremented database identifiers across all database adapters, the user seeding procedure explicitly queries back created projectUsers records to map user identifiers before creating dependent notification preferences. Sources: apps/web/scripts/dev/seed.ts:187-198
Note
All seeded user accounts share a uniform preset password hash derived from the plaintext string "password" via hashPassword("password"). Sources: apps/web/scripts/dev/seed.ts:163
The local development environment provides specialized scripts to seed partner-specific data structures, including test affiliate accounts, referral program applications, and financial commission records. These scripts execute against the active Prisma client instance configured via dotenv-flow/config. Sources: apps/web/scripts/dev/seed-application-events.ts:1-3, apps/web/scripts/dev/seed-partner-enrollment.ts:1-4, apps/web/scripts/dev/seed-commissions.ts:1-4
The seed-partner-enrollment.ts script constructs a complete partner profile graph anchored to a designated program ID (prog_1K2J9DRWPPJ2F1RX53N92TSGA) and referring partner ID (pn_1K2J9DRWPPJ2F1RX53N92TSGG). Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:6-8
prisma.program.findUnique() — Queries the target program to retrieve its defaultGroupId. Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:10-17nanoid(3) & createId() — Generates a unique 3-character suffix and prefixed identifiers (user_, pn_, pge_) for the new account. Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:24-29prisma.user.create() — Registers the user with an email following the format partner-{suffix}@dub-internal-test.com and links their defaultPartnerId. Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:31-39prisma.partner.create() — Establishes the core partner profile entity. Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:41-47prisma.partnerUser.create() — Binds the user to the partner organization with an owner role and initializes notification preferences. Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:49-58prisma.programEnrollment.create() — Enrolls the partner into the target program with a pending status. Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:60-68prisma.programApplicationEvent.create() — Logs an immediate application event with direct referral source and current timestamps. Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:70-83Note
The enrollment script targets the hardcoded program identifier prog_1K2J9DRWPPJ2F1RX53N92TSGA and terminates execution immediately with an error log if the program record is absent from the database. Sources: apps/web/scripts/dev/seed-partner-enrollment.ts:6, apps/web/scripts/dev/seed-partner-enrollment.ts:19-22
The seed-application-events.ts script populates historical ProgramApplicationEvent records for ACME program participants using randomized temporal offsets and distribution pools. Sources: apps/web/scripts/dev/seed-application-events.ts:27-97
The script queries program enrollments excluding the reserved partner ID pn_1K2J9DRWPPJ2F1RX53N92TSGH, calculates randomized timestamps spanning a 30-day window, determines approval status via a 70% probability threshold (Math.random() < 0.7), and commits the batch via prisma.programApplicationEvent.createMany(). Sources: apps/web/scripts/dev/seed-application-events.ts:31-92
The seed-commissions.ts script inserts mock financial earnings records for testing payout dashboards and ledger reconciliations. Sources: apps/web/scripts/dev/seed-commissions.ts:6-41
const commissions: Prisma.CommissionCreateManyInput[] = [
{
id: createId({ prefix: "cm_" }),
programId,
partnerId,
type: "referral",
amount: 0,
quantity: 1,
earnings: 10000,
createdAt: new Date(),
},
{
id: createId({ prefix: "cm_" }),
programId,
partnerId,
type: "referral",
amount: 0,
quantity: 1,
earnings: 20000,
createdAt: new Date(),
},
];
await prisma.commission.createMany({
data: commissions,
skipDuplicates: true,
});Local development of external provider connections requires provisioning integration metadata and installing fixture credentials into the database via dedicated setup scripts. Environment loading is initialized through dotenv-flow/config across script entry points. Sources: apps/web/scripts/dev/seed-integration.ts:5, apps/web/scripts/create-integration.ts:4
The create-integration.ts script registers third-party definitions in the database by performing an upsert operation on the prisma.integration model using GOOGLE_ADS_INTEGRATION_ID. Sources: apps/web/scripts/create-integration.ts:7-9
Note
The upsert strategy ensures safe re-execution during local environment resets by updating existing record fields (name, slug, description, verified, category) if the integration ID already exists. Sources: apps/web/scripts/create-integration.ts:7-30
The seed-integration.ts script provisions workspace-level installed integration instances using prisma.installedIntegration.upsert() with a composite unique key constraint (userId_integrationId_projectId). Sources: apps/web/scripts/dev/seed-integration.ts:8-15
await prisma.installedIntegration.upsert({
where: {
userId_integrationId_projectId: {
userId: "cl7p1s07k000687rbuhpwqkqa",
integrationId: INTERCOM_INTEGRATION_ID,
projectId: ACME_WORKSPACE_ID,
},
},
create: {
userId: "cl7p1s07k000687rbuhpwqkqa",
integrationId: INTERCOM_INTEGRATION_ID,
projectId: ACME_WORKSPACE_ID,
credentials: {
appId: "xxx",
accessToken: encrypt("xxx"),
},
},
update: {
//
},
});Warning
Sensitive integration secrets like access tokens must be passed through the application encryption helper function (encrypt("xxx")) before being committed to the credentials JSONB payload column. Sources: apps/web/scripts/dev/seed-integration.ts:22
Stripe application local development configuration is structured via extension inheritance in packages/stripe-app/stripe-app.dev.json, which inherits properties directly from the base stripe-app.json manifest. Sources: packages/stripe-app/stripe-app.dev.json:1-3
Populating large-volume partner data, generating vector embeddings, and running latency benchmarks require specialized initialization scripts. These tools manage atomic chunked database insertions, backfill search indices across partitioned program loops, and evaluate retrieval performance. Sources: apps/web/scripts/dev/seed-100k-partners.ts:1-7, apps/web/scripts/dev/upsert-emoji-embeddings.ts:1-84, apps/web/scripts/partners/backfill-partner-search.ts:1-25, apps/web/scripts/dev/benchmark-partner-search.ts:1-26
The seed-100k-partners.ts script writes User, Partner, PartnerUser, ProgramEnrollment, PartnerPlatform, Link, and ProgramPartnerTag rows in atomic chunks defined by CHUNK_SIZE (2,500 partners per chunk). To prevent accidental data corruption or production exposure, safety checks enforce strict environment rules. Sources: apps/web/scripts/dev/seed-100k-partners.ts:1-49
Caution
The seeding script inserts login-capable users with a known password. It refuses non-local DATABASE_URL connections unless --allowRemoteDatabase is explicitly passed, and refuses production environments (NODE_ENV or VERCEL_ENV) outright. Sources: apps/web/scripts/dev/seed-100k-partners.ts:18-20
Once seeded, partner enrollments must be indexed into the search provider using backfill-partner-search.ts. This utility pages through programs in ID order via iterateProgramIds() and pages through enrollments by ID rather than offset to keep per-batch costs flat. Sources: apps/web/scripts/partners/backfill-partner-search.ts:1-70
async function* iterateProgramIds(afterProgram?: string) {
let cursor = afterProgram;
let inclusive = Boolean(afterProgram);
while (true) {
const programs = await prisma.program.findMany({
where: cursor ? { id: inclusive ? { gte: cursor } : { gt: cursor } } : {},
select: { id: true },
orderBy: { id: "asc" },
take: PROGRAM_PAGE_SIZE,
});
if (programs.length === 0) {
return;
}
for (const { id } of programs) {
yield id;
}
cursor = programs[programs.length - 1].id;
inclusive = false;
if (programs.length < PROGRAM_PAGE_SIZE) {
return;
}
}
}The upsert-emoji-embeddings.ts script fetches emoji datasets from EMOJIBASE_DATA_URL, processes and deduplicates records by hexcode, and upserts them into an Upstash vector index in batches of UPSERT_BATCH_SIZE (500 records). Sources: apps/web/scripts/dev/upsert-emoji-embeddings.ts:5-84
const EMOJIBASE_DATA_URL =
"https://cdn.jsdelivr.net/npm/emojibase-data@16.0.3/en/data.json";
const UPSERT_BATCH_SIZE = 500;
type EmojiVectorRecord = {
id: string;
data: string;
metadata: {
emoji: string;
label: string;
};
};Note
Execution requires UPSTASH_VECTOR_EMOJI_REST_URL and UPSTASH_VECTOR_EMOJI_REST_TOKEN environment variables to be configured, failing immediately if either is missing. Sources: apps/web/scripts/dev/upsert-emoji-embeddings.ts:67-74
The benchmark-partner-search.ts script measures p99 latency for relevance-ranked partner lists. It calls getPartners in process to bypass HTTP routing, authentication, and serialization overhead, establishing a baseline floor for API performance. Sources: apps/web/scripts/dev/benchmark-partner-search.ts:1-9
Sources: apps/web/scripts/dev/benchmark-partner-search.ts:44-56, apps/web/scripts/dev/benchmark-partner-search.ts:97-190
Tip
Passing --searchOnly times the search provider's candidate query alone while skipping the local database. This permits a higher sample size ceiling (MAX_SEARCH_ONLY_SAMPLE_SIZE of 10,000) and enables direct provider comparison without local database bottlenecks. Sources: apps/web/scripts/dev/benchmark-partner-search.ts:10-12, apps/web/scripts/dev/benchmark-partner-search.ts:54, apps/web/scripts/dev/benchmark-partner-search.ts:169-178