Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/3.table-modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,16 @@ class MyContainerModifier extends ContainerModifier<{}, { fallback?: string }> {
}
```

### Attach a modifier

A decorator attaches its modifier with `attachModifier(target.constructor, Modifier, propertyKey, options)`, as the examples above do. TypeScript checks `options` against the modifier's options type. It does not check that the field names a member of the class.

A modifier with `lock` replaces the field with an accessor that routes reads and writes through the field's modifiers. `attachModifier` throws an `Error` when such a modifier goes on `constructor` or `__proto__`, or on a field that already has a one-way modifier. A modifier without `lock` only handles events, and the field keeps its plain value.

### Read a modified field in a query

`unlockrequest(table, row, field, modifiers)` reads a field unlocked inside a database query. It reads `table` only for its modifier metadata. Each two-way modifier on the field passes the stored value through its own `unlockrequest()`, with the arguments that `modifiers` gives for it by class name. A field with only one-way modifiers yields its stored value as it is. `unlockrequest` throws a `TypeError` when `modifiers` has no entry for a two-way modifier on the field. TypeScript checks that `field` is a key of the row, not each entry's arguments.

## Modifier events

A modifier hooks into a row's lifecycle through methods named after events. The engine calls each one with the modifier as `this`, then the table instance, the field name, and the event's arguments:
Expand Down
45 changes: 45 additions & 0 deletions src/modifiers/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,32 @@ type ExtractModifierOptions<T> =
type ExtractModifierArgs<T> =
T extends OneWayModifier<any, infer Args> ? Args : [];

/**
* Attaches a modifier to a field of a table class. A property decorator calls
* it with what it holds: `attachModifier(target.constructor, Modifier,
* propertyKey, options)`.
*
* At compile time, `options` must match the options type of `Modifier`.
* `TableClass` is any function and `field` any property key: nothing checks
* that `field` names a member of the class.
*
* At run time, a modifier with `lock` replaces the field with an accessor on
* `TableClass.prototype` that routes reads and writes through the field's
* modifiers. A modifier without `lock` only handles events, and the field
* keeps its plain value. Each method of the modifier other than `lock` and
* `unlock` becomes a handler for the event of the same name.
*
* @param TableClass The table class, `target.constructor` in a property
* decorator.
* @param Modifier The modifier class. The engine creates one instance per
* attachment and identifies it by its class name.
* @param field The field to modify, `propertyKey` in a property decorator.
* @param options The modifier's options, read through `this.options`.
* @throws Error when a modifier with `lock` is attached to `constructor` or
* `__proto__`.
* @throws Error when a modifier with `lock` is attached to a field that
* already has a one-way modifier, one with `lock` and no `unlock`.
*/
export function attachModifier<M extends Constructible<Modifier>>(
TableClass: Function,
Modifier: M,
Expand Down Expand Up @@ -397,6 +423,25 @@ type ModifierWithProxyArgs = {
args: unknown[];
};

/**
* Builds a query expression that reads a field unlocked, for use inside a
* database query. Each two-way modifier on the field passes the stored value
* through its own `unlockrequest`. The expression yields the stored value as
* it is when the field has only one-way modifiers, and the plain field when
* it has no modifier.
*
* At compile time, `field` must be a key of the row type. Nothing ties
* `table` to the row type, and nothing checks each entry's `args` against
* its modifier's arguments.
*
* @param table The table class. It is read only for its modifier metadata.
* @param object The row in the query.
* @param field The field to read.
* @param modifiers The arguments for each two-way modifier on the field,
* matched to it by class name.
* @throws TypeError when `modifiers` has no entry for a two-way modifier on
* the field.
*/
export function unlockrequest<T extends {}, K extends keyof T>(
table: Constructible,
object: DatabaseDev.ValueProxy<T>,
Expand Down
Loading