Dentolize · Dynamic Clinic Seeder Walkthrough
On this pageBusiness viewTechnical view

Parameters, safety & idempotency

Business view

The seeder is a script you point at a database. Three things keep that safe and predictable:

  1. Parameters let you shape the clinic — country, size, history length,

naming style — without editing code.

  1. A safety guard stops you seeding a shared or remote database by accident.
  2. 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 varFlagDefaultMeaning
COUNTRY--countrySAPreset: SA / AE / EG (countries.js:7)
BRANCHES--branches2Branch count (min 1)
DOCTORS--doctors4Doctors (min 1)
STAFF--staff4Support staff (min 0)
PATIENTS--patients80Patients (min 1)
AVG_OPS--avgOps4~average visits/patient
MONTHS--months12History span (min 1)
CHART--chartDENTALClinical chart type
NAMING--namingrealisticrealistic or descriptive
SEED--seed20260530RNG seed (determinism)
COMPANY_NAME--companyNamederivedCompany display name
LOGIN_NAME--loginNamederivedCompany login slug
OWNER_EMAIL--ownerEmailowner@<login>.seedOwner email + idempotency key
PASSWORD--passwordDentolize@2026Shared login password (config.js:88)
CONFIRM--confirmoffAllow 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:

CodeCurrencyVATDialInsurers (first 3 seeded)
SASAR15%+966Bupa Arabia, Tawuniya, MedGulf
AEAED5%+971Daman, AXA Gulf, Oman Insurance
EGEGP14%+20Misr 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:

  1. 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).

  1. Reprints the summary + credential blocks (index.js:83-88).
  2. 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).