Skip to content

Latest commit

 

History

History
268 lines (200 loc) · 7.95 KB

6.listable.md

File metadata and controls

268 lines (200 loc) · 7.95 KB
title Listable fields
description Choose the fields a list route returns with @Listable, serve several field sets with pluck modes, page the results, and sort them with @Sortable.
mode reference

Overview

The @Listable decorator selects the fields that list responses include. A list route returns only the fields marked as listable, so list payloads stay small. Pluck modes let one controller serve several field sets, and every list response carries pagination metadata.

The @Sortable decorator marks the fields that a list request can sort on.

Basic usage

A list response includes the fields decorated with @Listable(). A field without the decorator is left out of list results, but a single-record get response still includes it when the field has read access.

import {
  Access,
  AccessMode,
  Listable,
  ModelReference,
} from "@antelopejs/interface-data-api/metadata";

@RegisterDataController()
class UserAPI extends DataController(
  User,
  DefaultRoutes.All,
  Controller("/users"),
) {
  @ModelReference()
  @Model(UserModel, "my-database")
  declare userModel: UserModel;

  @Listable()
  @Access(AccessMode.ReadOnly)
  declare _id: string;

  @Listable()
  @Access(AccessMode.ReadOnly)
  declare email: string;

  @Access(AccessMode.ReadOnly)
  declare biography: string; // Excluded from list, included in get
}

A request to GET /users/list returns _id and email for each record. A request to GET /users/get?id=user-123 returns all three fields.

Pluck modes

A pluck mode is a named field set. With pluck modes, one controller serves several list views, each with its own fields. Each @Listable call adds the field to one mode.

Default mode

@Listable() without a mode name adds the field to the default "list" mode.

@Listable()
declare name: string;

Custom modes

@Listable(true, "detailed") adds the field to the "detailed" mode.

@Listable()                    // Included in default "list" mode
@Listable(true, "detailed")   // Also included in "detailed" mode
declare name: string;

@Listable(true, "detailed")   // Only in "detailed" mode
declare internalNotes: string;

@Listable()                    // Only in default "list" mode
declare email: string;

Exclude from a mode

@Listable(false, "minimal") leaves the field out of the "minimal" mode.

@Listable()
@Listable(false, "minimal")   // Excluded from "minimal" mode
declare description: string;

Access pluck modes via routes

Each pluck mode needs its own route. DefaultRoutes.WithOptions sets the route's pluckMode option.

const routes = {
  list: DefaultRoutes.List,
  detailed: DefaultRoutes.WithOptions(DefaultRoutes.List, {
    pluckMode: "detailed",
  }),
};

@RegisterDataController()
class UserAPI extends DataController(User, routes, Controller("/users")) {
  @ModelReference()
  @Model(UserModel, "my-database")
  declare userModel: UserModel;

  @Listable()
  @Listable(true, "detailed")
  @Access(AccessMode.ReadOnly)
  declare name: string;

  @Listable(true, "detailed")
  @Access(AccessMode.ReadOnly)
  declare internalNotes: string;
}
  • GET /users/list returns only name.
  • GET /users/detailed returns both name and internalNotes.

Modes are independent field sets. The "detailed" mode does not inherit the fields of "list", so the example adds name to both modes. A registered list route whose pluckMode no field declares returns 400 Bad Request, unless the route sets noPluck. A URL that matches no route returns 404.

Listable with getters

A getter (computed property) reads fields that the database must return. Its @Listable decorator lists those database fields, so the list query fetches them.

@Listable(["firstName", "lastName"])
@Access(AccessMode.ReadOnly)
get fullName() {
  return `${this.firstName} ${this.lastName}`;
}

Pagination

Every list response carries pagination metadata. The response has this shape:

{
  results: T[];     // Records for the current page
  total: number;    // Total number of matching records
  offset: number;   // Number of records skipped
  limit: number;    // Maximum records per page
}

Query parameters

Parameter Default Description
limit The route's maxPage, or 10 when it sets none Maximum number of records to return
offset 0 Number of records to skip

Example

GET /users/list?limit=2&offset=4
{
  "results": [
    { "_id": "user-5", "name": "Eve" },
    { "_id": "user-6", "name": "Frank" }
  ],
  "total": 100,
  "offset": 4,
  "limit": 2
}

Limit maximum page size

The maxPage route option caps the limit parameter. A request whose limit is larger than maxPage gets maxPage records at most. A request without limit also gets maxPage records. A route without maxPage caps limit at 100.

const routes = {
  list: DefaultRoutes.WithOptions(DefaultRoutes.List, { maxPage: 25 }),
};

@RegisterDataController()
class UserAPI extends DataController(User, routes, Controller("/users")) {
  // A request with ?limit=100 returns at most 25 records
}

Sorting with @Sortable

The @Sortable decorator lets a list request sort on the field.

import { Sortable } from "@antelopejs/interface-data-api/metadata";

@RegisterDataController()
class UserAPI extends DataController(
  User,
  DefaultRoutes.All,
  Controller("/users"),
) {
  @ModelReference()
  @Model(UserModel, "my-database")
  declare userModel: UserModel;

  @Listable()
  @Access(AccessMode.ReadOnly)
  @Sortable()
  declare _id: string;

  @Listable()
  @Access(AccessMode.ReadOnly)
  @Sortable({ noIndex: true })
  declare name: string;

  @Listable()
  @Access(AccessMode.ReadOnly)
  @Sortable({ noIndex: true })
  declare age: number;
}

Sort query parameters

Parameter Description
sortKey The field name to sort by. The field must have @Sortable.
sortDirection "asc" (ascending) or "desc" (descending). A missing or other value sorts ascending.

The noIndex option

By default, @Sortable() marks the field for indexed ordering. Set noIndex: true when the field has no index. Both settings build a database orderBy operation, and neither sorts rows in application memory. The option changes where the list query places the sort. The database provider decides how it runs the ordering.

@Sortable()                    // Uses database index for sorting
declare _id: string;

@Sortable({ noIndex: true })   // Non-indexed query ordering
declare name: string;

Localized fields

A @Localized field sorts on its value in the request's locale, which the controller's @ModifierKey(LocalizationModifier) field holds. A row with no value in that locale, or an empty one, sorts on its fallbackLocale value. The list query computes this value for each row, so the sort never uses an index, whatever noIndex says.

Example

GET /users/list?sortKey=age&sortDirection=desc&limit=3
{
  "results": [
    { "_id": "user-1", "name": "Senior", "age": 65 },
    { "_id": "user-2", "name": "Adult", "age": 30 },
    { "_id": "user-3", "name": "Young", "age": 18 }
  ],
  "total": 3,
  "offset": 0,
  "limit": 3
}

If sortKey names a field without @Sortable, the API returns 400 Bad Request with the message Field is not sortable.

Next steps

To link records across tables, see Foreign keys.