Skip to main content

Naming things

Names live forever. The cost of renaming a column is high; the cost of picking the wrong name today is permanent.

Drafted from planning · v0.1

General principles

  • Be specific: dropped_off_at not date1
  • Use the operator's vocabulary: bike-shop staff say "ticket," not "work order"
  • Avoid prefix soup: customer_id not cust_id not c_id
  • Plural for tables, singular for entities: customers table, "customer" in prose
  • No reserved words: avoid SQL reserved words even if technically allowed

Tables

snake_case, plural noun:

  • customers, service_tickets, inventory_skus, staff_sessions
  • Subordinate tables: {parent}_{child}customer_bikes, service_ticket_lines

Avoid:

  • tbl_customers (the tbl_ is noise)
  • customer (singular — should be customers)
  • customer_table (redundant)

Columns

snake_case. Suffix conventions:

  • *_id for FK references (always integer)
  • *_at for TEXT timestamps in ISO 8601 (e.g., created_at, paid_at)
  • *_on for DATE values (e.g., dob, promised_on)
  • *_cents for money (always integer, never float)
  • *_count for counters
  • is_* for boolean (stored as INTEGER 0/1)
  • qty_* for quantities (e.g., qty_on_hand, qty_committed)
  • *_json for serialized JSON TEXT columns (e.g., before_json)
  • *_hash for hashed values

Avoid:

  • created_date, created_time — use created_at (ISO 8601)
  • price — use price_cents (avoids float rounding bugs)
  • description when something more specific exists — issue_description, internal_notes

Endpoints

/api/{plural-resource}/{id}/{sub-resource}. Always lowercase, hyphen-separated.

  • /api/customers — list/create
  • /api/customers/123 — detail
  • /api/customers/123/bikes — sub-collection
  • /api/customers/123/bikes/456 — sub-detail
  • /api/customers/123/merge — action (POST)
  • /api/tickets/today — special view

Use POST for non-idempotent actions; PUT for idempotent updates; DELETE for deletion; GET for reads.

Functions

camelCase. Verb-noun pattern for handlers:

  • apiCustomersList, apiCustomersCreate, apiCustomersDetail
  • apiTicketsCreate, apiTicketsStatusTransition

Helpers:

  • hashPin, verifyPin, taxForLine, withAudit
  • pathParam, json, notFound

Don't:

  • getCustomers (too generic; conflicts with HTTP method)
  • processStuff (what stuff?)
  • customerHandler (one of many; doesn't say what it does)

Files

kebab-case.js. Match the contents:

  • src/lib/tax-bc.js for BC tax helpers
  • public/js/customers.js for the Customers screen enhancer
  • migrations/004-service-tickets.sql for slice 4 schema
  • docs/architecture/c4-context.mdx for the C4 context diagram

Constants

SCREAMING_SNAKE_CASE only for module-level true constants:

  • ADMIN_RESET_CODE
  • PBKDF2_ITERATIONS
  • SESSION_MAX_SECONDS

Configuration values that vary per shop go in shop_config, not in code constants.

Status enumerations

Lower-snake-case strings stored in TEXT columns:

  • Ticket status: dropped_off, quoted, in_progress, awaiting_parts, ready, picked_up, cancelled
  • Transaction status: pending, paid, voided, refunded_partial, refunded_full

Don't use integers ("status 3 means ready"). Strings are self-documenting; the small storage cost is irrelevant.

Audit event actions

{entity}.{verb}, lower-snake:

  • customer.created, customer.updated, customer.merged
  • ticket.status, ticket.line.added, ticket.cashed_out
  • permission.overridden

The verb tells what happened; the entity tells what changed.

When you can't decide

Pick the name that:

  1. The operator would use in conversation
  2. Future-you reading the field in 2 years would understand
  3. Greps well (avoid common-word names like name, type, kind without prefix)

If still stuck: pick one, ship it, rename if it bothers you in 2 weeks. Renaming is cheap when the name is fresh.

See also