Conventions


TypeScript

Request & response parameters

  • Timestamps end with at. Use the past tense of the verb. For example, createdAt and expiredAt.
  • Prefer enums instead of boolean. For example, instead of completed: boolean, use status: "completed" | "started".
  • Use optional timestamps instead of booleans. For example, instead of completed: boolean, use completedAt?: number.
  • Refer to the ID of the current object as just id (not somethign like userId).
  • Do not use id in the variable name if passing an id. For example, use datacenter instead of datacenterId.
    • If storing an ID, make sure that the variable name references the name of the corresponding data. For example ownerUser -> User or datacenter -> Datacenter.
  • Format dates with RFC 3339 (which is a profile of ISO 8601) in the format of 2024-07-06T04:56:49.517Z. This provides a balance of human-readable & machine-readable dates.
    • Do not include timezones in dates. Always use UTC with Z (not +00:00).
    • Include milliseconds in dates.
  • Common property names:
    • id
    • createdAt & destroyedAt
    • name Used for human-readable name.
    • slug If there is a short string used to refer to this object.
  • Format durations in milliseconds, except in the uncommon case of performance-sensitive code. In that case, use microseconds.
  • For any other cases, delegate to Stripe's naming semantics.

Use interfaces & functions, not classes

Use exclusively interfaces & standalone functions internally. This keeps code clean, legible, and easy to refactor at the cost of slightly more verbosity.

Examples: Project, Registry, Module, Script

Use classes sparingly

Prefer to use interfaces and vanilla functions over classes.

Because we're working with simple data structures, the using interfaces & functions makes code simpler & easier to maintain.

Interfaces allow for building complex types without having to handle constructors & getters.

Camel case + acryonyms

Follow camel case strictly & treat acronyms as single words.

Examples:

  • Prefer OpenApi instead of OpenAPI
  • Prefer Uuid instead of UUID

Externally tagged enums

When representing an enum with associated data (often called "sum types" which are a kind of algebreic data type, ADT), represent using a nested object (often called "externally tagged enums").

This comes at the expense of not having exhaustive switch statements.

Externally tagged enums are easy to represent in languages that don't support advanced type constraints, such as C# and most OpenAPI SDK generators (i.e. don't support oneOf).

This example:

type MyEnum = { foo: MyEnumFoo } | { bar: MyEnumBar } | { baz: MyEnumBaz };

interface MyEnumFoo {

}

interface MyEnumBar {

}

interface MyEnumBaz {

}
TypeScript

Can be represented in C# like this:

class MyEnum {
    MyEnumFoo? Foo;
    MyEnumBar? Bar;
    MyEnumBaz? Baz;
}

class MyEnumFoo {
}

class MyEnumBar {
}

class MyEnumBaz {
}
C#

Database

Uses of id included with type

When referring to the ID of the current type, use id. When referring to a foreign type, use {type name}Id.

Example:

model User {
    id    String @id @default(uuid()) @db.Uuid
    posts Post[]
}

model Post {
    id     String @id @default(uuid()) @db.Uuid
    userId String @db.Uuid
    user   User   @relation(fields: [userId], references: [id])
}
Prisma