@antelopejs/interface-database-decorators 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"schema.test.js","sourceRoot":"","sources":["../../src/tests/schema.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.test.js","sourceRoot":"","sources":["../../src/tests/schema.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ # Interface Database Decorators
2
+
3
+ ## Overview
4
+
5
+ The Interface Database Decorators package provides a structured, decorator-based framework for defining database tables, applying field-level modifiers (encryption, hashing, localization), building data models with CRUD operations, and injecting models into API controllers. It builds on top of the `@antelopejs/interface-database` package and integrates with the `@antelopejs/interface-api` package for controller parameter injection.
6
+
7
+ ## Dependencies
8
+
9
+ This package depends on the following:
10
+
11
+ - [**@antelopejs/interface-database**](https://github.com/AntelopeJS/interface-database) - The underlying database query interface (AQL)
12
+ - [**@antelopejs/interface-api**](https://github.com/AntelopeJS/interface-api) - API controller integration for parameter decoration
13
+ - [**@antelopejs/interface-core**](https://github.com/AntelopeJS/interface-core) - Core decorator utilities
14
+ - [**reflect-metadata**](https://www.npmjs.com/package/reflect-metadata) - Metadata reflection for TypeScript decorators
15
+ - [**randomstring**](https://www.npmjs.com/package/randomstring) - Random string generation for hash salts
16
+
17
+ ## Documentation
18
+
19
+ - [Table Definitions](./2.table-definitions.md) - Define database tables with the `Table` class, `Index` decorator, and `Fixture` decorator
20
+ - [Table Modifiers](./3.table-modifiers.md) - Apply encryption, hashing, and localization modifiers to table fields, or create custom modifiers
21
+ - [Data Models](./4.data-models.md) - Create data models with `BasicDataModel` for CRUD operations and data conversion
22
+ - [Parameter Decoration](./5.parameter-decoration.md) - Inject model instances into API controller methods with the `Model` decorator
@@ -0,0 +1,260 @@
1
+ # Table Definitions
2
+
3
+ The Database Decorators package provides a class-based approach to defining database tables. You extend the `Table` base class, declare each field with TypeScript's `declare` keyword and the `@Field` decorator, and use further decorators to configure indexes, relations, and initial data.
4
+
5
+ ## The Table Class
6
+
7
+ The `Table` class serves as the base class for all table definitions. Every table automatically includes an `_id` field as the primary key.
8
+
9
+ ```typescript
10
+ import { Table, Field } from "@antelopejs/interface-database-decorators";
11
+
12
+ class User extends Table {
13
+ @Field("string")
14
+ declare name: string;
15
+
16
+ @Field("string")
17
+ declare email: string;
18
+ }
19
+ ```
20
+
21
+ ### Extend with Modifiers
22
+
23
+ The `Table.with()` static method incorporates modifier mixins into the table class, adding capabilities like encryption, hashing, or localization:
24
+
25
+ ```typescript
26
+ import { Table, Field, EncryptionModifier, Encrypted } from "@antelopejs/interface-database-decorators";
27
+
28
+ class SensitiveData extends Table.with(EncryptionModifier) {
29
+ @Field("string")
30
+ declare publicContent: string;
31
+
32
+ @Encrypted({ secretKey: process.env.SECRET_KEY || "default-key" })
33
+ @Field("string")
34
+ declare secureContent: string;
35
+ }
36
+ ```
37
+
38
+ For detailed information about modifiers, see [Table Modifiers](./3.table-modifiers.md).
39
+
40
+ ## The Index Decorator
41
+
42
+ The `Index` decorator marks a field as a database index. Indexed fields enable efficient lookups through `getAll()` and `between()` on the underlying AQL table.
43
+
44
+ ```typescript
45
+ import { Table, Field, Index } from "@antelopejs/interface-database-decorators";
46
+
47
+ class User extends Table {
48
+ @Index()
49
+ @Field("string")
50
+ declare email: string;
51
+
52
+ @Field("string")
53
+ declare name: string;
54
+ }
55
+ ```
56
+
57
+ ### Options
58
+
59
+ | Option | Type | Description |
60
+ | ------- | -------- | ----------------------------------------------- |
61
+ | `group` | `string` | Assign the field to a named compound index group. |
62
+
63
+ ### Compound Indexes
64
+
65
+ Assign multiple fields to the same index group to create a compound index:
66
+
67
+ ```typescript
68
+ import { Table, Field, Index, Relation } from "@antelopejs/interface-database-decorators";
69
+
70
+ class User extends Table {
71
+ @Field("string")
72
+ declare name: string;
73
+ }
74
+
75
+ class UserActivity extends Table {
76
+ @Index({ group: "user_action" })
77
+ @Field("string")
78
+ @Relation({ to: () => User })
79
+ declare userId: string;
80
+
81
+ @Index({ group: "user_action" })
82
+ @Field("string")
83
+ declare action: string;
84
+
85
+ @Field("date")
86
+ declare timestamp: Date;
87
+ }
88
+ ```
89
+
90
+ ## The Field Decorator
91
+
92
+ `@Field(type)` declares the field type for a property. The type is forwarded verbatim into the `TableDefinition.fields` map handed to the adapter. It accepts the full `FieldType` union -- primitive tokens (`"string"`, `"number"`, ...), arrays, nested records, or an io-ts codec.
93
+
94
+ ```typescript
95
+ import * as t from "io-ts";
96
+ import { Table, Field, Index, RegisterTable } from "@antelopejs/interface-database-decorators";
97
+
98
+ @RegisterTable("orders", "shop")
99
+ class Order extends Table {
100
+ @Field("string")
101
+ declare id: string;
102
+
103
+ @Field(t.union([t.literal("open"), t.literal("closed")]))
104
+ declare status: "open" | "closed";
105
+
106
+ @Index()
107
+ @Field("number")
108
+ declare total: number;
109
+ }
110
+ ```
111
+
112
+ Properties without `@Field` are omitted from `fields`. `@Field` stacks freely with `@Index`.
113
+
114
+ ## The Relation Decorator
115
+
116
+ The `Relation` decorator declares a link from a field to another table. It is **declarative metadata only**: database implementations do not enforce it (no foreign key constraint, no referential validation). Introspection tooling consumes it to expose the links between tables — for example to render schema diagrams or navigate related records.
117
+
118
+ ```typescript
119
+ import { Table, Field, Index, Relation } from "@antelopejs/interface-database-decorators";
120
+
121
+ class Comment extends Table {
122
+ @Index()
123
+ @Field("string")
124
+ @Relation({ to: () => Post })
125
+ declare postId: string;
126
+
127
+ @Field("string")
128
+ @Relation({ to: () => Tag, many: true })
129
+ declare tagIds: string[];
130
+ }
131
+ ```
132
+
133
+ ### Options
134
+
135
+ | Option | Type | Description |
136
+ | --------- | ------------------- | ---------------------------------------------------------------------- |
137
+ | `to` | `() => typeof Table` | Thunk returning the target table class (lazy to allow forward references). |
138
+ | `toField` | `string` | Target field name. Defaults to the target table's primary key. |
139
+ | `many` | `boolean` | The decorated field holds multiple target keys (many targets per source record). |
140
+
141
+ `@Relation` stacks freely with `@Field` and `@Index`.
142
+
143
+ ## The RegisterTable Decorator
144
+
145
+ The `RegisterTable` class decorator associates a table class with a specific table name and schema. This registration is used by `RegisterSchema` to build the schema definition automatically.
146
+
147
+ ```typescript
148
+ import { Table, Field, Index, RegisterTable } from "@antelopejs/interface-database-decorators";
149
+
150
+ @RegisterTable("users", "myapp")
151
+ class User extends Table {
152
+ @Index()
153
+ @Field("string")
154
+ declare email: string;
155
+
156
+ @Field("string")
157
+ declare name: string;
158
+ }
159
+ ```
160
+
161
+ The first argument is the table name in the database, and the second is the schema name.
162
+
163
+ ## The Fixture Decorator
164
+
165
+ The `Fixture` decorator defines default data to insert when a table is first created. It receives a generator function that produces initial records.
166
+
167
+ ```typescript
168
+ import { Table, Field, Fixture } from "@antelopejs/interface-database-decorators";
169
+
170
+ @Fixture(() => [
171
+ { _id: "admin", name: "Administrator" },
172
+ { _id: "user", name: "Standard User" },
173
+ { _id: "guest", name: "Guest" },
174
+ ])
175
+ class UserRole extends Table {
176
+ @Field("string")
177
+ declare name: string;
178
+ }
179
+ ```
180
+
181
+ The generator function:
182
+
183
+ - Receives the table class as its parameter.
184
+ - Can return a single object, an array of objects, or a `Promise` resolving to either.
185
+ - Runs only when the table is empty, preventing duplicate inserts on subsequent starts.
186
+
187
+ ### Async Generators
188
+
189
+ ```typescript
190
+ @Fixture(async () => {
191
+ const defaults = await loadDefaultConfig();
192
+ return defaults.map((cfg) => ({
193
+ _id: cfg.key,
194
+ value: cfg.value,
195
+ }));
196
+ })
197
+ class SystemConfig extends Table {
198
+ @Field("string")
199
+ declare value: string;
200
+ }
201
+ ```
202
+
203
+ ## Inheritance
204
+
205
+ Tables support standard class inheritance. Define a base table with common fields and extend it for specific use cases:
206
+
207
+ ```typescript
208
+ import { Table, Field, Index, Relation } from "@antelopejs/interface-database-decorators";
209
+
210
+ class BaseEntity extends Table {
211
+ @Field("date")
212
+ declare createdAt: Date;
213
+
214
+ @Field("date")
215
+ declare updatedAt: Date;
216
+ }
217
+
218
+ class User extends BaseEntity {
219
+ @Index()
220
+ @Field("string")
221
+ declare email: string;
222
+
223
+ @Field("string")
224
+ declare firstName: string;
225
+
226
+ @Field("string")
227
+ declare lastName: string;
228
+ }
229
+
230
+ class Post extends BaseEntity {
231
+ @Index()
232
+ @Field("string")
233
+ @Relation({ to: () => User })
234
+ declare authorId: string;
235
+
236
+ @Field("string")
237
+ declare title: string;
238
+
239
+ @Field("string")
240
+ declare content: string;
241
+ }
242
+ ```
243
+
244
+ ## Register a Schema
245
+
246
+ The `RegisterSchema` function builds a `SchemaDefinition` from all tables registered for the given schema id (via `@RegisterTable`), constructs the `Schema` (which the adapter provisions), then inserts any fixture data for tables that are empty.
247
+
248
+ ```typescript
249
+ import { RegisterSchema } from "@antelopejs/interface-database-decorators";
250
+
251
+ await RegisterSchema("myapp");
252
+ ```
253
+
254
+ ### Parameters
255
+
256
+ | Parameter | Type | Description |
257
+ | ---------- | -------- | ----------------------------------------------------------------- |
258
+ | `schemaId` | `string` | Schema id matching the `@RegisterTable(_, schemaId)` declarations. |
259
+
260
+ `RegisterSchema` returns `Promise<void>`.
@@ -0,0 +1,349 @@
1
+ # Table Modifiers
2
+
3
+ Table modifiers transform field values as they are stored in and retrieved from the database. They enable transparent encryption, hashing, localization, and custom data transformations. Add modifiers to a table class using the `Table.with()` method.
4
+
5
+ ## Built-in Modifiers
6
+
7
+ ### EncryptionModifier
8
+
9
+ The `EncryptionModifier` encrypts field values before storing them and decrypts them on retrieval. Both operations happen transparently.
10
+
11
+ ```typescript
12
+ import { Table, Field, EncryptionModifier, Encrypted } from "@antelopejs/interface-database-decorators";
13
+
14
+ class UserCredentials extends Table.with(EncryptionModifier) {
15
+ @Encrypted({
16
+ secretKey: process.env.ENCRYPTION_KEY || "default-key",
17
+ algorithm: "aes-256-gcm",
18
+ ivSize: 16,
19
+ })
20
+ @Field("string")
21
+ declare creditCardNumber: string;
22
+
23
+ @Encrypted({ secretKey: process.env.ENCRYPTION_KEY || "default-key" })
24
+ @Field("any")
25
+ declare personalData: {
26
+ ssn: string;
27
+ birthDate: string;
28
+ };
29
+ }
30
+ ```
31
+
32
+ #### How It Works
33
+
34
+ - Data is encrypted using the specified algorithm before database insertion.
35
+ - A random initialization vector (IV) is generated for each write.
36
+ - For authenticated encryption algorithms (e.g., `aes-256-gcm`), an auth tag is stored alongside the ciphertext.
37
+ - On retrieval, the data is automatically decrypted.
38
+ - Date objects are preserved through JSON serialization with a custom encoder.
39
+
40
+ #### Options
41
+
42
+ | Option | Type | Default | Description |
43
+ | ----------- | -------- | ----------------- | -------------------------------- |
44
+ | `secretKey` | `string` | (required) | The encryption key. |
45
+ | `algorithm` | `string` | `"aes-256-gcm"` | Encryption algorithm. |
46
+ | `ivSize` | `number` | `16` | Initialization vector size in bytes. |
47
+
48
+ ### HashModifier
49
+
50
+ The `HashModifier` stores a one-way hash of field values. Unlike encryption, hashing is irreversible -- you cannot retrieve the original value. Use this for passwords and other sensitive data that only needs equality verification.
51
+
52
+ ```typescript
53
+ import { Table, Field, HashModifier, Hashed } from "@antelopejs/interface-database-decorators";
54
+
55
+ class User extends Table.with(HashModifier) {
56
+ @Field("string")
57
+ declare email: string;
58
+
59
+ @Hashed({ algorithm: "sha256" })
60
+ @Field("string")
61
+ declare password: string;
62
+
63
+ verifyPassword(plainPassword: string): boolean {
64
+ return this.testHash("password", plainPassword);
65
+ }
66
+ }
67
+ ```
68
+
69
+ #### How It Works
70
+
71
+ - A random salt is generated and stored in the field's metadata on first write.
72
+ - The value is hashed with the salt using the specified algorithm.
73
+ - The `testHash` method (added by the mixin) hashes a candidate value with the stored salt and compares the result.
74
+
75
+ #### Options
76
+
77
+ | Option | Type | Default | Description |
78
+ | ----------- | -------- | ---------- | ------------------ |
79
+ | `algorithm` | `string` | `"sha256"` | Hashing algorithm. |
80
+
81
+ #### Mixin Method
82
+
83
+ The `HashModifier` mixin adds a `testHash` method to table instances:
84
+
85
+ ```typescript
86
+ testHash(field: string, value: unknown): boolean
87
+ ```
88
+
89
+ Returns `true` if `value`, when hashed, matches the stored hash for the given field.
90
+
91
+ ### LocalizationModifier
92
+
93
+ The `LocalizationModifier` stores multiple language versions of a field value. Access a specific locale by calling `localize()` on the table instance.
94
+
95
+ ```typescript
96
+ import { Table, Field, LocalizationModifier, Localized } from "@antelopejs/interface-database-decorators";
97
+
98
+ class Product extends Table.with(LocalizationModifier) {
99
+ @Field("number")
100
+ declare price: number;
101
+
102
+ @Localized({ fallbackLocale: "en" })
103
+ @Field("string")
104
+ declare name: string;
105
+
106
+ @Localized({ fallbackLocale: "en" })
107
+ @Field("string")
108
+ declare description: string;
109
+ }
110
+ ```
111
+
112
+ #### Usage
113
+
114
+ ```typescript
115
+ const product = new Product();
116
+ product._id = "prod-123";
117
+ product.price = 99.99;
118
+
119
+ // Set localized content
120
+ product.localize("en").name = "Premium Headphones";
121
+ product.localize("fr").name = "Casque Premium";
122
+ product.localize("es").name = "Auriculares Premium";
123
+
124
+ // Read localized content
125
+ const spanish = product.localize("es");
126
+ console.log(spanish.name); // "Auriculares Premium"
127
+
128
+ // Fallback to default locale when translation is missing
129
+ const german = product.localize("de");
130
+ console.log(german.name); // "Premium Headphones" (fallback)
131
+ ```
132
+
133
+ #### Wildcard Key
134
+
135
+ The wildcard key `"*"` retrieves or sets all translations at once:
136
+
137
+ ```typescript
138
+ // Get all translations
139
+ const allNames = product.localize("*").name;
140
+ // { en: "Premium Headphones", fr: "Casque Premium", es: "Auriculares Premium" }
141
+
142
+ // Set all translations
143
+ product.localize("*").name = {
144
+ en: "Premium Headphones",
145
+ fr: "Casque Premium",
146
+ es: "Auriculares Premium",
147
+ de: "Premium-Kopfhoerer",
148
+ };
149
+ ```
150
+
151
+ #### Options
152
+
153
+ | Option | Type | Description |
154
+ | ---------------- | -------- | ----------------------------------------------------- |
155
+ | `fallbackLocale` | `string` | Locale to use when the requested locale is unavailable. |
156
+
157
+ #### Mixin Method
158
+
159
+ The `LocalizationModifier` mixin adds a `localize` method:
160
+
161
+ ```typescript
162
+ localize(locale: string, fields?: Array<keyof this>): this
163
+ ```
164
+
165
+ Sets the active locale for accessing localized fields. If `fields` is provided, only those fields are unlocked for the specified locale.
166
+
167
+ ### AutoDateModifier
168
+
169
+ The `AutoDateModifier` automatically populates timestamp fields during insert and update operations. Use the `CreationTime` and `UpdateTime` decorators to mark fields.
170
+
171
+ ```typescript
172
+ import { Table, CreationTime, UpdateTime } from "@antelopejs/interface-database-decorators";
173
+
174
+ class Article extends Table {
175
+ declare title: string;
176
+
177
+ @CreationTime()
178
+ declare creationDate: Date;
179
+
180
+ @UpdateTime()
181
+ declare updateDate: Date;
182
+ }
183
+ ```
184
+
185
+ #### How It Works
186
+
187
+ - On insert, both `CreationTime` and `UpdateTime` fields are set to the current date.
188
+ - On update, `UpdateTime` fields are refreshed while `CreationTime` fields are removed from the payload so the original creation date is preserved.
189
+ - This is an event-only modifier: it does not transform stored values and does not require adding a mixin through `Table.with()`.
190
+
191
+ ## Combine Multiple Modifiers
192
+
193
+ Pass multiple modifier classes to `Table.with()`:
194
+
195
+ ```typescript
196
+ import {
197
+ Table,
198
+ Field,
199
+ EncryptionModifier,
200
+ HashModifier,
201
+ LocalizationModifier,
202
+ Encrypted,
203
+ Hashed,
204
+ Localized,
205
+ } from "@antelopejs/interface-database-decorators";
206
+
207
+ class UserProfile extends Table.with(EncryptionModifier, HashModifier, LocalizationModifier) {
208
+ @Encrypted({ secretKey: process.env.SECRET_KEY || "default-key" })
209
+ @Field("any")
210
+ declare privateInfo: {
211
+ address: string;
212
+ phoneNumber: string;
213
+ };
214
+
215
+ @Hashed()
216
+ @Field("string")
217
+ declare password: string;
218
+
219
+ @Localized({ fallbackLocale: "en" })
220
+ @Field("string")
221
+ declare bio: string;
222
+ }
223
+ ```
224
+
225
+ ## Create Custom Modifiers
226
+
227
+ ### OneWayModifier
228
+
229
+ A `OneWayModifier` transforms data in one direction (when writing to the database). Use it for irreversible transformations like hashing.
230
+
231
+ ```typescript
232
+ import { OneWayModifier, MixinSymbol, attachModifier } from "@antelopejs/interface-database-decorators";
233
+ import { MakePropertyDecorator } from "@antelopejs/interface-core/decorators";
234
+ import { createHash } from "crypto";
235
+
236
+ class PrefixHashModifier extends OneWayModifier<string | undefined, [], {}, { algorithm?: string }> {
237
+ public readonly autolock = true;
238
+
239
+ public override lock(_locked: string | undefined, value: unknown) {
240
+ if (value === undefined) return undefined;
241
+ return createHash(this.options.algorithm || "sha256")
242
+ .update(JSON.stringify(value))
243
+ .digest("hex");
244
+ }
245
+
246
+ [MixinSymbol] = class {};
247
+ }
248
+
249
+ const PrefixHashed = MakePropertyDecorator(
250
+ (target, propertyKey, options?: { algorithm?: string }) => {
251
+ attachModifier(target.constructor, PrefixHashModifier, propertyKey, options || {});
252
+ },
253
+ );
254
+ ```
255
+
256
+ The generic parameters for `OneWayModifier<LockedType, Args, Meta, Options>` are:
257
+
258
+ | Parameter | Description |
259
+ | ------------ | --------------------------------------------------- |
260
+ | `LockedType` | The type stored in the database after transformation |
261
+ | `Args` | Additional arguments for `lock` and `test` |
262
+ | `Meta` | Per-field metadata stored alongside the value |
263
+ | `Options` | Configuration passed via the decorator |
264
+
265
+ ### TwoWayModifier
266
+
267
+ A `TwoWayModifier` extends `OneWayModifier` with an `unlock` method for reversible transformations like encryption:
268
+
269
+ ```typescript
270
+ import { TwoWayModifier, MixinSymbol, attachModifier } from "@antelopejs/interface-database-decorators";
271
+ import { MakePropertyDecorator } from "@antelopejs/interface-core/decorators";
272
+
273
+ class Base64Modifier extends TwoWayModifier<string, [], {}, { prefix?: string }> {
274
+ public readonly autolock = true;
275
+ public readonly autounlock = true;
276
+
277
+ public override lock(_locked: string | undefined, value: unknown) {
278
+ if (value === undefined) return undefined;
279
+ const prefix = this.options.prefix || "b64-";
280
+ return `${prefix}${Buffer.from(JSON.stringify(value)).toString("base64")}`;
281
+ }
282
+
283
+ public override unlock(locked: string) {
284
+ const prefix = this.options.prefix || "b64-";
285
+ const encoded = locked.substring(prefix.length);
286
+ return JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
287
+ }
288
+
289
+ public override unlockrequest(data: any) {
290
+ return data;
291
+ }
292
+
293
+ [MixinSymbol] = class {};
294
+ }
295
+
296
+ const Base64Encoded = MakePropertyDecorator(
297
+ (target, propertyKey, options?: { prefix?: string }) => {
298
+ attachModifier(target.constructor, Base64Modifier, propertyKey, options || {});
299
+ },
300
+ );
301
+ ```
302
+
303
+ ### ContainerModifier
304
+
305
+ For modifiers that store multiple values in a key-value structure (like `LocalizationModifier`), extend `ContainerModifier`. It provides built-in support for the wildcard key `"*"` for bulk operations:
306
+
307
+ ```typescript
308
+ import { ContainerModifier } from "@antelopejs/interface-database-decorators";
309
+
310
+ class MyContainerModifier extends ContainerModifier<{}, { fallback?: string }> {
311
+ // Override unlock() to add fallback behavior
312
+ // Override unlockrequest() for query-time data access
313
+ }
314
+ ```
315
+
316
+ ## Modifier Events
317
+
318
+ Modifiers can hook into the data lifecycle by implementing event handler methods. These methods are called during data conversion and database operations:
319
+
320
+ | Event | Triggered When |
321
+ | --------------- | ----------------------------------------- |
322
+ | `fromPlainData` | After converting plain data to an instance |
323
+ | `toPlainData` | After converting an instance to plain data |
324
+ | `fromDatabase` | After reading from the database |
325
+ | `toDatabase` | Before writing to the database |
326
+ | `insert` | Before inserting a new record |
327
+ | `update` | Before updating an existing record |
328
+
329
+ ```typescript
330
+ import { Modifier, MixinSymbol } from "@antelopejs/interface-database-decorators";
331
+
332
+ class TimestampModifier extends Modifier {
333
+ public insert(instance: any, field: string) {
334
+ if (field === "createdAt" && !instance[field]) {
335
+ instance[field] = new Date();
336
+ }
337
+ }
338
+
339
+ public update(instance: any, field: string) {
340
+ if (field === "updatedAt") {
341
+ instance[field] = new Date();
342
+ }
343
+ }
344
+
345
+ [MixinSymbol] = class {};
346
+ }
347
+ ```
348
+
349
+ Event-only modifiers (without `lock`/`unlock`) are valid and useful for side effects like logging, validation, or auto-populating fields.