@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.
- package/dist/modifiers/autodate.d.ts +32 -0
- package/dist/modifiers/autodate.js +49 -0
- package/dist/modifiers/autodate.js.map +1 -0
- package/dist/modifiers/index.d.ts +1 -0
- package/dist/modifiers/index.js +1 -0
- package/dist/modifiers/index.js.map +1 -1
- package/dist/tests/modifiers/autodate.test.d.ts +1 -0
- package/dist/tests/modifiers/autodate.test.js +106 -0
- package/dist/tests/modifiers/autodate.test.js.map +1 -0
- package/dist/tests/modifiers/common.test.js +45 -0
- package/dist/tests/modifiers/common.test.js.map +1 -1
- package/dist/tests/modifiers/encryption.test.js +9 -0
- package/dist/tests/modifiers/encryption.test.js.map +1 -1
- package/dist/tests/modifiers/hash.test.js +2 -0
- package/dist/tests/modifiers/hash.test.js.map +1 -1
- package/dist/tests/modifiers/localization.test.js +7 -0
- package/dist/tests/modifiers/localization.test.js.map +1 -1
- package/dist/tests/schema.test.js +19 -0
- package/dist/tests/schema.test.js.map +1 -1
- package/docs/1.introduction.md +22 -0
- package/docs/2.table-definitions.md +260 -0
- package/docs/3.table-modifiers.md +349 -0
- package/docs/4.data-models.md +183 -0
- package/docs/5.parameter-decoration.md +155 -0
- package/package.json +15 -3
- package/skills/database-decorators-interface/SKILL.md +99 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.test.js","sourceRoot":"","sources":["../../src/tests/schema.test.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,6EAG0D;AAC1D,6EAG0D;AAC1D,
|
|
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.
|