Parameters, safety & idempotency
Business view
The seeder is a script you point at a database. Three things keep that safe and predictable:
- Parameters let you shape the clinic — country, size, history length,
naming style — without editing code.
- A safety guard stops you seeding a shared or remote database by accident.
- Idempotency means re-running against an existing sandbox refreshes the login
instead of piling up duplicate clinics.
Technical view
Parameters · config.js:57-86
Each parameter is read from a CLI flag or an env var; the flag wins (config.js:32-45). Counts are clamped to sane minimums.
| Env var | Flag | Default | Meaning |
|---|---|---|---|
COUNTRY | --country | SA | Preset: SA / AE / EG (countries.js:7) |
BRANCHES | --branches | 2 | Branch count (min 1) |
DOCTORS | --doctors | 4 | Doctors (min 1) |
STAFF | --staff | 4 | Support staff (min 0) |
PATIENTS | --patients | 80 | Patients (min 1) |
AVG_OPS | --avgOps | 4 | ~average visits/patient |
MONTHS | --months | 12 | History span (min 1) |
CHART | --chart | DENTAL | Clinical chart type |
NAMING | --naming | realistic | realistic or descriptive |
SEED | --seed | 20260530 | RNG seed (determinism) |
COMPANY_NAME | --companyName | derived | Company display name |
LOGIN_NAME | --loginName | derived | Company login slug |
OWNER_EMAIL | --ownerEmail | owner@<login>.seed | Owner email + idempotency key |
PASSWORD | --password | Dentolize@2026 | Shared login password (config.js:88) |
CONFIRM | --confirm | off | Allow a non-local DB |
Doc-vs-claim gap. The PR body describes the sandbox "small profile" as 1 branch, 2 doctors, 12 patients, 3 months. Those are overrides the sandbox CLI passes, not the code defaults (2 / 4 / 80 / 12mo above). Both the screenshots in the Feature Tour and the counts you see (12 patients, 1 "Riyadh Clinic" branch, 2 doctors) confirm the sandbox runs the small profile.
Country presets · countries.js:7-43
A preset fixes currency, VAT rate, dial code, timezone, Faker locale, city list, and insurer list — so financial math and localization are country-consistent and never leak from Faker:
| Code | Currency | VAT | Dial | Insurers (first 3 seeded) |
|---|---|---|---|---|
SA | SAR | 15% | +966 | Bupa Arabia, Tawuniya, MedGulf |
AE | AED | 5% | +971 | Daman, AXA Gulf, Oman Insurance |
EG | EGP | 14% | +20 | Misr Insurance, AXA Egypt, MetLife Egypt |
An unknown code falls back to SA (countries.js:43).
Determinism · rng.js:15-48
createRng builds one Faker instance, localized (Arabic first with English + base fallback so no field is ever empty, rng.js:18) and seeded once (rng.js:20). Every random draw in the whole seeder — numbers, picks, dates, chances — goes through this module; nothing calls Math.random directly. Same SEED + same params ⇒ identical clinic. dateInHistory produces business-hours datetimes within the history window, never in the future (rng.js:36-46).
The safety guard · config.js:105-113
const isLocalHost = h => h === 'localhost' || h === '127.0.0.1' || h === '::1'
export const assertSafeTarget = config => {
if (isLocalHost(config.db.host) || config.confirmed) return
throw new Error(`Refusing to seed non-local database "…". Re-run with --confirm …`)
}
The target host/database is parsed from DATABASE_URL (config.js:47-55) and printed in the banner before any writes (config.js:93). The guard runs on both the seed path and the cleanup path (index.js:58,64).
Idempotency · index.js:71-91
If OWNER_EMAIL resolves to an existing user, the seeder:
- Resets every user of that company to the configured password (the sandbox
promises one unified password across all roster logins), and marks them validated (index.js:78-82).
- Reprints the summary + credential blocks (index.js:83-88).
- Returns — no second company.
A wiped volume comes back empty, so the lookup misses and a full seed runs. This is what makes "redeploy refreshes the login; wipe rebuilds the clinic" true.
Cleanup · report.js:76-97
--cleanup <companyId> validates the id is a UUID, then within a single transaction disables FK triggers (SET session_replication_role = replica), deletes relation-child rows whose tables lack a companyId (skipping tables absent from the schema, so it's safe on a base-schema DB), deletes every companyId table, and finally the company. Best-effort: if a residual FK blocks the final delete, the guidance is to drop the database and re-seed (report.js:54-58).