<!-- Markdown mirror of https://justscale.sh/docs/models/field-builders -->

# Field Builders

Comprehensive reference for all field types and modifiers

Field builders provide a type-safe, fluent API for defining model field types and constraints. Each builder corresponds to a PostgreSQL data type and provides relevant modifiers.

## String and Text

### field.string()

Variable-length string field, maps to `VARCHAR` in PostgreSQL. Use `.max(n)` to set maximum length or `.fixed(n)` for fixed-length `CHAR`.

string-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class User extends defineModel({
  email: field.string().max(255).unique(),
  username: field.string().max(50).index(),
  countryCode: field.string().fixed(2),  // CHAR(2)
  bio: field.string().optional(),        // No max length
}) {}
```

### field.text()

Unlimited-length text field for long content, maps to `TEXT`.

text-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class BlogPost extends defineModel({
  title: field.string().max(200),
  content: field.text(),           // Long content
  summary: field.text().optional(),
}) {}
```

## Numbers

### field.int()

Standard integer field, maps to `INTEGER` (32-bit signed, range -2,147,483,648 to 2,147,483,647).

int-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Product extends defineModel({
  quantity: field.int().default(0),
  viewCount: field.int().default(0),
  priority: field.int().optional(),
}) {}
```

### field.smallint()

Small integer field, maps to `SMALLINT` (16-bit signed, range -32,768 to 32,767). Use for smaller numbers to save space.

smallint-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Rating extends defineModel({
  score: field.smallint(),  // 1-5 rating
  stars: field.smallint(),  // 1-10 stars
}) {}
```

### field.bigint()

Large integer field, maps to `BIGINT` (64-bit signed). TypeScript type is `bigint`.

bigint-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Analytics extends defineModel({
  totalViews: field.bigint().default(0n),
  uniqueVisitors: field.bigint().default(0n),
}) {}

const stats = new Analytics({
  totalViews: 9007199254740991n,  // Beyond Number.MAX_SAFE_INTEGER
});
```

### field.decimal(precision, scale)

Fixed-precision decimal, maps to `NUMERIC(precision, scale)`. Stored as `string`in TypeScript to preserve exact precision. Essential for monetary values.

decimal-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Order extends defineModel({
  subtotal: field.decimal(10, 2),      // Max: 99,999,999.99
  tax: field.decimal(10, 2),
  total: field.decimal(10, 2),
  discountPercent: field.decimal(5, 2).optional(), // Max: 999.99
}) {}

const order = new Order({
  subtotal: '99.99',
  tax: '8.50',
  total: '108.49',
});
```

### field.float() / field.double()

Floating-point numbers. `float()` maps to `REAL` (32-bit),`double()` maps to `DOUBLE PRECISION` (64-bit). Avoid for money.

float-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Measurement extends defineModel({
  temperature: field.float(),
  latitude: field.double(),
  longitude: field.double(),
  precision: field.double().optional(),
}) {}
```

## Boolean

### field.boolean()

Boolean field, maps to `BOOLEAN`. Often paired with `.default()`.

boolean-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class User extends defineModel({
  isActive: field.boolean().default(true),
  emailVerified: field.boolean().default(false),
  isPremium: field.boolean().optional(),
}) {}
```

## UUID

### field.uuid()

UUID field, maps to `UUID`. TypeScript type is `string`. Often used for external IDs or references.

uuid-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class ApiKey extends defineModel({
  keyId: field.uuid().unique(),
  secret: field.string().max(255),
  userId: field.uuid().index(),
}) {}
```

## Date and Time

### field.timestamp()

Timestamp with timezone, maps to `TIMESTAMP WITH TIME ZONE`. TypeScript type is `Date`.

timestamp-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Event extends defineModel({
  name: field.string(),
  scheduledAt: field.timestamp(),
  cancelledAt: field.timestamp().optional(),
}) {}
```

### field.date()

Date only (no time), maps to `DATE`. TypeScript type is `Date`.

date-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Person extends defineModel({
  name: field.string(),
  birthDate: field.date(),
  hireDate: field.date().optional(),
}) {}
```

### field.time()

Time of day (no date), maps to `TIME`. TypeScript type is `string` (HH:MM:SS format).

time-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Schedule extends defineModel({
  name: field.string(),
  startTime: field.time(),
  endTime: field.time(),
}) {}

const shift = new Schedule({
  name: 'Morning Shift',
  startTime: '09:00:00',
  endTime: '17:00:00',
});
```

## JSON

### field.json() / field.jsonb()

JSON fields for structured data. `json()` maps to `JSON`,`jsonb()` maps to `JSONB` (binary, indexable). Provide a TypeScript type parameter for type safety.

json-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

interface ProductMetadata {
  dimensions: { width: number; height: number; depth: number };
  tags: string[];
  features: string[];
}

class Product extends defineModel({
  name: field.string(),
  // JSONB for indexable structured data
  metadata: field.jsonb<ProductMetadata>(),
  // JSON for simple unstructured data
  settings: field.json<Record<string, unknown>>().optional(),
}) {}

const product = new Product({
  name: 'Laptop',
  metadata: {
    dimensions: { width: 14, height: 10, depth: 1 },
    tags: ['electronics', 'computer'],
    features: ['Backlit keyboard', 'Touch screen'],
  },
});
```

## Binary

### field.bytes()

Binary data field, maps to `BYTEA`. TypeScript type is `Uint8Array`.

bytes-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class File extends defineModel({
  filename: field.string().max(255),
  mimeType: field.string().max(100),
  content: field.bytes(),
}) {}
```

## Enums

### field.enum(name, values)

PostgreSQL enum field. Provide enum name and array of string values. TypeScript infers a union type from the values.

enum-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

const USER_ROLES = ['admin', 'editor', 'viewer'] as const;
const ORDER_STATUSES = ['pending', 'processing', 'completed', 'cancelled'] as const;

class User extends defineModel({
  email: field.string().max(255),
  role: field.enum('user_role', USER_ROLES).default('viewer'),
}) {}

class Order extends defineModel({
  orderNumber: field.string().max(50),
  status: field.enum('order_status', ORDER_STATUSES).default('pending'),
}) {}

// TypeScript enforces valid enum values
const user = new User({
  email: 'admin@example.com',
  role: 'admin',  // Type-safe
  // role: 'superuser',  // TypeScript error
});
```

## Arrays

### field.array(elementType)

PostgreSQL array field. Pass another field builder as the element type. TypeScript type is `T[]`.

array-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Article extends defineModel({
  title: field.string(),
  tags: field.array(field.string()),
  scores: field.array(field.int()).optional(),
  relatedIds: field.array(field.uuid()),
}) {}

const article = new Article({
  title: 'Getting Started',
  tags: ['tutorial', 'beginner'],
  scores: [4, 5, 3],
  relatedIds: ['uuid-1', 'uuid-2'],
});
```

## Objects

### field.object(shape)

Nested object stored as JSONB. Define the shape using field builders. TypeScript infers the complete nested type.

object-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Product extends defineModel({
  name: field.string(),
  dimensions: field.object({
    width: field.float(),
    height: field.float(),
    depth: field.float(),
    unit: field.string().max(10),
  }),
  shippingInfo: field.object({
    weight: field.decimal(10, 2),
    carrier: field.string().optional(),
  }).optional(),
}) {}

const product = new Product({
  name: 'Box',
  dimensions: {
    width: 10.5,
    height: 5.0,
    depth: 8.0,
    unit: 'cm',
  },
});
```

## References

### field.ref(Model)

Reference to another model (many-to-one or one-to-one). Use a function for self-references to avoid hoisting issues. See

TypeScript

```typescript
Repositories > References
```

for full documentation.

ref-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class User extends defineModel({
  email: field.string(),
  name: field.string(),
}) {}

class Post extends defineModel({
  title: field.string(),
  author: field.ref(User),           // Reference<User>
  parent: field.ref(() => Post),     // Self-reference
}) {}
```

### field.refs(Model)

Reference to multiple models (many-to-many). Creates a join table in the repository.

refs-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Tag extends defineModel({
  name: field.string().max(50).unique(),
}) {}

class Post extends defineModel({
  title: field.string(),
  content: field.text(),
  tags: field.refs(Tag),  // References<Tag> - many-to-many
}) {}
```

## System Fields

Special semantic fields for common lifecycle and versioning patterns:

### field.createdAt()

Timestamp automatically set when the record is inserted. Maps to `TIMESTAMP WITH TIME ZONE`.

### field.updatedAt()

Timestamp automatically updated whenever the record is saved. Maps to `TIMESTAMP WITH TIME ZONE`.

### field.deletedAt()

Optional timestamp for soft deletes. When set, the record is considered deleted but remains in the database. Automatically marked as optional.

### field.version()

Optimistic concurrency control version number. Automatically incremented on updates. Defaults to 1.

system-fields-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Document extends defineModel({
  title: field.string(),
  content: field.text(),
  createdAt: field.createdAt(),   // Auto-set on insert
  updatedAt: field.updatedAt(),   // Auto-updated on save
  deletedAt: field.deletedAt(),   // Soft delete (optional)
  version: field.version(),       // Optimistic locking (defaults to 1)
}) {}
```

## Field Modifiers

All field builders support these common modifiers:

### .optional()

Makes the field nullable. TypeScript type becomes `T | undefined`.

optional-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class User extends defineModel({
  email: field.string(),            // Required
  bio: field.string().optional(),   // Optional (can be undefined)
  age: field.int().optional(),      // number | undefined
}) {}

const user = new User({ email: 'test@example.com' });
// bio and age are undefined
```

### .default(value)

Sets a default value when the field is not provided. Can be a static value or a function.

default-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class User extends defineModel({
  email: field.string(),
  isActive: field.boolean().default(true),
  balance: field.decimal(10, 2).default('0.00'),
  joinedAt: field.timestamp().default(() => new Date()),
}) {}
```

### .unique()

Adds a unique constraint. PostgreSQL enforces uniqueness across all rows.

unique-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class User extends defineModel({
  email: field.string().max(255).unique(),
  username: field.string().max(50).unique(),
}) {}
```

### .index()

Creates a database index for faster lookups. Use for frequently queried fields.

index-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Post extends defineModel({
  title: field.string(),
  slug: field.string().max(255).unique().index(),
  authorId: field.uuid().index(),  // Foreign key index
  publishedAt: field.timestamp().index(),
}) {}
```

### .backfill(value)

Provides a value for existing rows when adding a new non-nullable field via migrations. Required when adding non-nullable fields without defaults to tables with existing data.

backfill-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

// Migration scenario: adding 'status' to existing User table
class User extends defineModel({
  email: field.string(),
  // Existing users will get 'active' status when column is added
  status: field.string().default('active').backfill('active'),
}) {}
```

## Chaining Modifiers

Modifiers can be chained together fluently. Order doesn't matter:

chaining-example.tsTypeScript

```typescript
import { defineModel, field } from '@justscale/core/models';

class Product extends defineModel({
  // Chain multiple modifiers
  sku: field.string()
    .max(50)
    .unique()
    .index(),

  name: field.string()
    .max(200)
    .index(),

  price: field.decimal(10, 2)
    .default('0.00'),

  description: field.text()
    .optional(),
}) {}
```

## Next Steps

- Models Overview
- Repositories Overview
- References
