ent-framework
  • Ent Framework
  • Getting Started
    • Code Structure
    • Connect to a Database
    • Create Ent Classes
    • VC: Viewer Context and Principal
    • Ent API: insert*()
    • Built-in Field Types
    • Ent API: load*() by ID
    • N+1 Selects Solution
    • Automatic Batching Examples
    • Ent API: select() by Expression
    • Ent API: loadBy*() Unique Key
    • Ent API: update*()
    • Ent API: deleteOriginal()
    • Ent API: count() by Expression
    • Ent API: exists() by Expression
    • Ent API: selectBy() Unique Key Prefix
    • Ent API: upsert*()
    • Privacy Rules
    • Validators
    • Triggers
    • Custom Field Types
  • Ent API: Configuration and Types
  • Scalability
    • Replication and Automatic Lag Tracking
    • Sharding and Microsharding
    • Sharding Terminology
    • Locating a Shard and ID Format
    • Sharding Low-Level API
    • Shard Affinity and Ent Colocation
    • Inverses and Cross Shard Foreign Keys
    • Shards Rebalancing and pg-microsharding Tool
    • Connection Pooling
  • Advanced
    • Database Migrations and pg-mig Tool
    • Ephemeral (Symbol) Fields
    • Atomic Updates and CAS
    • Custom Field Refactoring
    • VC Flavors
    • Query Cache and VC Caches
    • Loaders and Custom Batching
    • PostgreSQL Specific Features
    • Query Planner Hints
    • Cluster Maintenance Queries
    • Logging and Diagnostic Tools
    • Composite Primary Keys
    • Passwords Rotation
  • Architecture
    • Abstraction Layers
    • Ent Framework, Meta’s TAO, entgo
    • JIT in SQL Queries Batching
    • To JOIN or not to JOIN
Powered by GitBook
On this page
  • Ent Class Static Properties
  • EntClass.SCHEMA
  • Helper (Input) Types
  • EntClass.VALIDATION
  • EntClass.CLUSTER
  • EntClass.SHARD_AFFINITY and .SHARD_LOCATOR
  • EntClass.TRIGGERS
  • EntClass and Ent Interfaces

Was this helpful?

Edit on GitHub

Ent API: Configuration and Types

Every Ent class exposes several static "constant" properties that you can use to get access to various Ent configuration features.

Ent Class Static Properties

Consider having the following Ent class defined:

const schema = new PgSchema(
  "users",
  {
    id: { type: ID, autoInsert: "nextval('users_id_seq')" },
    email: { type: String },
  },
  ["email"],
);

export class EntUser extends BaseEnt(cluster, schema) {
  static override configure() {
    return new this.Configuration({
      shardAffinity: GLOBAL_SHARD,
      privacyInferPrincipal: async (_vc, row) => row.id,
      privacyLoad: [new AllowIf(new OutgoingEdgePointsToVC("id"))],
      privacyInsert: [],
    });
  }
}

EntClass.SCHEMA

In the example above, EntUser.SCHEMA it is equal to BaseEnt's schema parameter. Each Schema has the following properties:

  • name: name of the underlying Ent table ("users" in the above example)

  • table: an stronly typed object that defines the table's shape. In our example it is { id: ..., email: ... } , exactly as defined in new PgSchema(...) code above.

  • uniqueKey: a strongly typed array of fields composing the Ent Schema's unique key. Again, exactly as defined in PgSchema above.

Examples:

// ["id", "email"]
const fields = Object.keys(EntUser.SCHEMA.table);

// Using the Row type and table name.
const master = await EntUser.CLUSTER.globalShard().client(MASTER);
const rows = await master.query<Row<typeof EntUser.SCHEMA.table>>({
  query: [`SELECT * FROM ${EntUser.SCHEMA.name} WHERE id=?`, userID],
  isWrite: false,
  annotations: [vc.toAnnotation()],
  op: "MY_SELECT",
  table: "users",
});

// Build a custom WHERE condition.
const where: Where<typeof EntUser.SCHEMA.table> = { 
  email: { $not: "test@example.com" },
};
await EntUser.select(vc, where, 100);

Notice how we used typeof EntUser.SCHEMA.table in the example above: it's a common pattern in Ent Framework. Most of the types it exposes (like Row, Where etc.) accept a generic TTable argument that can be obtained with this construction.

Helper (Input) Types

Ent Framework API methods like insert*(), update*(), load*(), select*() etc. accept strongly-typed input and return strongly typed Ents. Here are some examples:

  • InsertInput<typeof EntUser.SCHEMA.table>: the shape of the argument that insert*() and upsert*() methods accept. This type plays nice with e.g. optional fields (the fields that have autoInsert in their definition), nulls etc.

  • UpdateInput<typeof EntUser.SCHEMA.table>: methods like update*() accept this shape. Since you can choose, which fields to update, all of the properties of that type are optional.

  • Row<typeof EntUser.SCHEMA.table> : that's a general shape of Ents returned from load*() and select*() calls. Notice that the type is very different from InsertInput, because it never has any optional fields. Optionality is the concept related to mutations; once you load something existing from the database, all of the fields are present, so they will all be "required". Don't mix up Row and InsertInput types in your code!

  • Where<typeof EntUser.SCHEMA.table>: a query that select() call accepts. It supports rich query language features like $not, $and, $lt etc. See more details in Ent API: select() by Expression.

There are some other, less frequently, used types as well. See the docblocks in Ent Framework source code for more details and examples.

EntClass.VALIDATION

This static Ent property allows you to manually run privacy and validation rules on an Ent without triggering an insert/update/delete. It is convenient if you want do a "dry-run" before applying an actual operation, to e.g. enable or disable some form controls or buttons in the user interface.

try {
  await EntUser.VALIDATION.validateUpdate(vc, user, {
    email: "new@example.com"
  });
} catch (e: unknown) {
  if (e instanceof EntAccessError) {
    // It's a top-level base class for all access related errors.
    if (e instanceof EntNotUpdatableError) {
      // Privacy rules failure with details.
      console.log(e.message);
    } else if (e instanceof EntValidatonError) {
      // Fields validation error.
      console.log(e.errors);
      console.log(e.toStandardSchemaV1()); // https://standardschema.dev
    } else {
      ...
    }
  } else {
    throw e;
  }
}

The methods available on VALIDATION property are:

  • validateInsert(vc, input: InsertInput<TTable>): checks what would happen if you try to insert a new Ent with such properties.

  • validateUpdate(vc, old: Row<TTable>, input: UpdateInput<TTable>, privacyOnly: boolean): we already mentioned this method in the example above. You can also pass the last privacyOnly parameter as true if you do not want to run user-defined fields validators and only need to recheck the privacy rules. Otherwise, by default, it runs both privacy rules and fields validators, which is almost always what we want.

  • validateDelete(vc, row: Row<TTable>): rarely used, checks what would happen if you try to delete that Ent.

Notice that Row<TTable> is not the same as an instance of your Ent (although you can pass an Ent to the functions that accept a Row type). Rows are a lower level concept: Row<TTable> represents a plain object, it's basically a strongly-typed TypeScript Record of fields and their values (including nullability concept, custom field types etc.). Rows don't have vc property, nor do they have any Ent specific methods.

And as mentioned above, TTable is derived from the Ent schema, e.g. typeof EntUser.SCHEMA.table.

EntClass.CLUSTER

This static property simply equals to cluster parameter of BaseEnt you are extending when defining your Ent class. Use it in case you need to access some low-level Cluster API:

const master = await EntUser.CLUSTER.globalShard().client(MASTER);

EntClass.SHARD_AFFINITY and .SHARD_LOCATOR

The SHAR_AFFINITY static property simply returns the value of shardAffinity configuration option.

The SHARD_LOCATOR property is pretty low-level: it exposes an Ent Framework object that allows to infer the affected microshards based on various criteria (like from an ID, or from a Where<TTable> clause, or from a list of IDs etc.): singleShardForInsert(), multiShardsFromInput(), singleShardFromID() etc. Those methods are aware of the Ent's Inverses (see Inverses and Cross Shard Foreign Keys), but we won't discuss them here much.

It also exposes a useful method allShards():

const userShards = EntUser.SHARD_LOCATOR.allShards();
for (const shard of userShards) {
  // do something with users on this shard
}

Depending on the Ent's shardAffinity, this method will return either one shard (if it's GLOBAL_SHARD) or all shards of the cluster (in case it's RANDOM_SHARD or some other affinity), thus, allowing you to iterate over all Ents of this type in the cluster. Read more about sharding in Shard Affinity and Ent Colocation.

EntClass.TRIGGERS

This static property exposes a Triggers objects that allows you to enumerate all of the Ent's trggers. It is almost never used externally, so we'll skip the details (see the source code if you want to learn more).

EntClass and Ent Interfaces

Sometimes you want to write a generic function that accepts any Ent of a particular shape, or any Ent class. You can use EntClass and Ent interfaces (type shapes) for this. Here are some pretty artificial examples:

async function fancyDelete<TTable extends Table & { key: String }>(
  ent: Ent<TTable>,
): Promise<void> {
  ...
  await deleteExternalResource(ent.key);
  await ent.deleteOriginal();
}

async function loadAny<TTable extends Table>(
  vc: VC,
  EntCls: EntClass<TTable>,
  id: string,
): Promise<Ent<TTable>> {
  return EntCls.loadIfReadableNullable(vc, id);
}

Unfortunately, due to some TypeScript limitations (incomplete mixins support and a lack of class static properties typing), the functionality of EntClass and Ent interfaces is limited. But keep them in mind still, since they may be useful.

PreviousCustom Field TypesNextReplication and Automatic Lag Tracking

Last updated 6 days ago

Was this helpful?