Diagram
100%

Drag to pan · Scroll to zoom · + / − to zoom · 0 to fit · Esc to close

Skip to content

2. Fields, Types, and Rows ​

A FieldType describes value interpretation. A Field<T> is a named, reusable token that carries that type plus storage, UI, seed, defaulting, and reference metadata.

Prefer semantic field tokens ​

dart
abstract final class AreaFields {
  static const code = CodeField();
  static const name = NameField();
  static const description = DescriptionField();
  static const tenantId = TenantIdField();
  static const siteId = SiteIdField();
}

CodeField, NameField, and TenantIdField encode shared conventions. Use primitive tokens when the semantics are genuinely local:

dart
static const capacity = IntegerField(
  'capacity',
  title: 'Capacity',
  nullable: true,
);

static const roomNumber = TextField(
  'room_number',
  title: 'Room Number',
  maxLength: 80,
);

static const calibratedAt = TimestampField(
  'calibrated_at',
  title: 'Calibrated At',
  withTimeZone: true,
);

Closed field types ​

The sealed interpretation core covers text, integers, big integers, doubles, exact decimals, booleans, timestamps, dates, wall-clock times, intervals, UUIDs, JSONB, binary values, PostGIS points, native ranges, network addresses, pgvector values, enums, and decisions. Higher-level tokens such as quantities, money, files, identifiers, and related fields compose those primitives.

Use DecimalField for regulated or financial precision. Its Dart boundary is a string so a binary floating-point conversion cannot silently change it.

Use one GeoPointField rather than independent latitude/longitude columns when the value is a location. BinaryField is for small bounded byte values; large documents remain object-storage records represented by FileField metadata. A FileField names the StorageBucket that holds its files with bucket:; the module declares the bucket with Module(buckets: [...]). The field's size limit and MIME types must fit inside the bucket's, and bucket names are unique across the Blueprint. See File storage for the routes, the client and the runtime tables. ObjectField<T> stores a registered Dart value as JSONB through an explicit ObjectFieldCodec<T>; it never turns an arbitrary runtime map into an executable Dart type. PostGIS fields require the postgis module extension and vector fields require vector.

Value familyField tokenDart boundaryPostgreSQL lowering
wall-clock timeTimeFieldLocalTimetime / timetz
durationIntervalFieldDurationinterval
bytesBinaryFieldBinaryValuebytea
pointGeoPointFieldGeoPointgeometry / geography(Point,srid)
scalar intervalrange field familyValueRange<T>native range type
networkInetField, CidrField, MacAddressFieldvalidated stringnative network type
embeddingVectorFieldfixed-length List<double>vector(n)
typed JSONObjectField<T>registered Tjsonb

The value wrappers have explicit portable codecs:

dart
final time = LocalTime.parse('08:30:00+05:30');
final point = GeoPoint(latitude: 17.385, longitude: 78.487);
final range = ValueRange<DateTime>(
  lower: DateTime.utc(2026, 1, 1),
  upper: DateTime.utc(2027, 1, 1),
);

print(time.toIso8601String());
print(point.toGeoJson());
print(range.toJson((value) => value.toIso8601String()));

These types flow through declaration, definition serialization, runtime validation, execution-plan casting, DDL, Explorer metadata, and field-registry mapping. Dedicated spatial-distance, nearest-neighbor, range-containment, and vector-similarity operators are not part of the current query vocabulary.

Enums keep wire values stable and labels human ​

dart
enum AreaType { room, suite, zone, corridor, airlock, wash_bay }

static const areaType = EnumField(
  'area_type',
  AreaType.values,
  dbEnumName: 'area_type',
  title: 'Area Type',
  ui: FieldUI(
    valueLabels: {'wash_bay': 'Wash Bay'},
  ),
);

The persisted value is wash_bay; the UI renders Wash Bay. Filters, tables, forms, chips, and details all consume the same label map.

Defaults belong at the correct layer ​

dart
static const id = UuidField(
  'id',
  nullable: false,
  defaultExpr: FieldDefaultExpr.uuidV4,
);

static const createdAt = TimestampField(
  'created_at',
  nullable: false,
  defaultExpr: FieldDefaultExpr.now,
);

static const createdBy = ActorField(
  'created_by',
  defaultExpr: FieldDefaultExpr.actorId,
);

Postgres can own uuidV4 and now. The authenticated runtime injects actorId and siteId; they cannot be honest SQL defaults.

Reference fields carry picker semantics ​

dart
static const areaCategoryId = UuidField(
  'area_category_id',
  title: 'Area Category',
  indexed: true,
  db: FieldDb(
    queryPatterns: [QueryPattern.filter, QueryPattern.aggregate],
  ),
  ui: FieldUI(
    widget: UIFieldControl.referencePicker,
    filterable: true,
  ),
  reference: UIReferenceHint(
    target: EntityRef(module: ModuleRef('ops'), name: 'area_category'),
    projection: 'picker',
  ),
);

The storage value remains a UUID. The generated editor uses an entity picker and the generated detail surface uses an entity link. A user should not have to type or interpret the raw ID.

Typed row access ​

The same token reads and writes a loose storage row safely:

dart
final row = Row(<String, Object?>{
  'code': 'WASH-01',
  'capacity': 12,
});

final code = row.require(AreaFields.code);       // String
final capacity = row.maybe(AreaFields.capacity); // int?

row.set(AreaFields.name, 'Equipment Wash Bay');

This is why field tokens must be reused rather than recreated from matching strings.

DB and UI hints do not change domain meaning ​

dart
static const name = TextField(
  'name',
  maxLength: 240,
  nullable: false,
  db: FieldDb(
    indexed: true,
    queryPatterns: [QueryPattern.search, QueryPattern.sort],
  ),
  ui: FieldUI(
    searchable: true,
    sortable: true,
    order: 10,
  ),
);

Hints guide interpreters. They do not create a second field definition.

Checkpoint ​

Create tokens for a controlled equipment record:

  • code and name;
  • serial number;
  • category reference;
  • decimal contact-surface area;
  • multiple document attachments;
  • lifecycle status enum.

Compare your result with the real Ops equipment.dart, then continue to Facets and entities.