From fe1c71df6fff3dc6658c822e8d0c5677ed7d8043 Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Tue, 10 Nov 2026 04:00:58 +0000 Subject: [PATCH] docs(modifiers): document attachModifier and unlockrequest --- docs/3.table-modifiers.md | 10 +++++++++ src/modifiers/common.ts | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/docs/3.table-modifiers.md b/docs/3.table-modifiers.md index 4ccbc6c..fececcb 100644 --- a/docs/3.table-modifiers.md +++ b/docs/3.table-modifiers.md @@ -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: diff --git a/src/modifiers/common.ts b/src/modifiers/common.ts index 4acd61d..5dbbf0e 100644 --- a/src/modifiers/common.ts +++ b/src/modifiers/common.ts @@ -287,6 +287,32 @@ type ExtractModifierOptions = type ExtractModifierArgs = T extends OneWayModifier ? 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>( TableClass: Function, Modifier: M, @@ -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( table: Constructible, object: DatabaseDev.ValueProxy,