@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
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Data Models
|
|
2
|
+
|
|
3
|
+
Data models provide a high-level interface for interacting with database tables. They handle conversion between database records and TypeScript class instances, and expose methods for common CRUD operations.
|
|
4
|
+
|
|
5
|
+
## BasicDataModel
|
|
6
|
+
|
|
7
|
+
The `BasicDataModel` function creates a model class for a specific table. It wraps the underlying AQL table with methods that automatically apply modifier transformations.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { BasicDataModel } from "@antelopejs/interface-database-decorators";
|
|
11
|
+
import { User } from "./user.table";
|
|
12
|
+
|
|
13
|
+
const UserModel = BasicDataModel(User, "users");
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Parameters
|
|
17
|
+
|
|
18
|
+
| Parameter | Type | Description |
|
|
19
|
+
| ----------- | ----------------- | ---------------------------------------------------- |
|
|
20
|
+
| `dataType` | `Constructible<T>`| The Table class representing your table |
|
|
21
|
+
| `tableName` | `string` | The table name in the database (optional if `@RegisterTable` is used) |
|
|
22
|
+
|
|
23
|
+
When the table class has been decorated with `@RegisterTable`, the `tableName` parameter can be omitted.
|
|
24
|
+
|
|
25
|
+
## Instantiate a Model
|
|
26
|
+
|
|
27
|
+
A model instance requires a `SchemaInstance` from the database interface:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { Schema } from "@antelopejs/interface-database";
|
|
31
|
+
|
|
32
|
+
const schema = Schema.get("myapp")!;
|
|
33
|
+
const db = schema.instance();
|
|
34
|
+
|
|
35
|
+
const userModel = new UserModel(db);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## CRUD Operations
|
|
39
|
+
|
|
40
|
+
### Read
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Get by primary key
|
|
44
|
+
const user = await userModel.get("user-123");
|
|
45
|
+
|
|
46
|
+
// Get by secondary index
|
|
47
|
+
const admins = await userModel.getBy("role", "admin");
|
|
48
|
+
|
|
49
|
+
// Get all documents
|
|
50
|
+
const allUsers = await userModel.getAll();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Create
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Insert a single document
|
|
57
|
+
const ids = await userModel.insert({
|
|
58
|
+
email: "alice@example.com",
|
|
59
|
+
name: "Alice",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Insert multiple documents
|
|
63
|
+
const batchIds = await userModel.insert([
|
|
64
|
+
{ email: "bob@example.com", name: "Bob" },
|
|
65
|
+
{ email: "carol@example.com", name: "Carol" },
|
|
66
|
+
]);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Update
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Update by ID
|
|
73
|
+
await userModel.update("user-123", { name: "Updated Name" });
|
|
74
|
+
|
|
75
|
+
// Update by including the primary key in the object
|
|
76
|
+
await userModel.update({ _id: "user-123", name: "Updated Name" });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> **Important:** When calling `update` without a separate ID argument, the object must include the primary key field.
|
|
80
|
+
|
|
81
|
+
### Delete
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
await userModel.delete("user-123");
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Data Conversion
|
|
88
|
+
|
|
89
|
+
Data models include static methods for converting between different data representations:
|
|
90
|
+
|
|
91
|
+
### `fromPlainData(obj)`
|
|
92
|
+
|
|
93
|
+
Convert a plain JavaScript object into a Table class instance. Modifier transformations (like encryption and hashing) are applied during this conversion.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const user = UserModel.fromPlainData({
|
|
97
|
+
_id: "user-123",
|
|
98
|
+
email: "user@example.com",
|
|
99
|
+
name: "John",
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `fromDatabase(obj)`
|
|
104
|
+
|
|
105
|
+
Convert a raw database record into a Table class instance. This attaches the class prototype and triggers the `fromDatabase` modifier event.
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const user = UserModel.fromDatabase(rawDbRecord);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `toDatabase(obj)`
|
|
112
|
+
|
|
113
|
+
Convert a Table class instance into a plain object suitable for database storage. Modifier events like `toDatabase` are triggered.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const dbReady = UserModel.toDatabase(userInstance);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Validation
|
|
120
|
+
|
|
121
|
+
`Model.validate(obj)` runs every io-ts codec attached via `@Field` against the matching property of `obj`. String-token fields and properties without `@Field` are skipped.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const result = UserModel.validate({ name: "Alice", age: 30 });
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
console.error(result.errors); // [{ field: "age", message: "not number" }, ...]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Pass `{ validate: true }` to `insert` or `update` to gate the write on validation. On failure the call throws an `Error` whose `errors` property holds the per-field details; on success the original object passes through unchanged.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
await userModel.insert({ name: "Alice", age: 30 }, { validate: true });
|
|
134
|
+
await userModel.update("user-123", { name: "Bob" }, { validate: true });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`insert` validates every annotated codec field. `update` is partial: only fields present on the patch object are checked, so omitting fields you don't intend to modify is fine. Use `Model.validate(obj, { partial: true })` for the same patch semantics outside of `update`.
|
|
138
|
+
|
|
139
|
+
## Extend BasicDataModel
|
|
140
|
+
|
|
141
|
+
Extend the generated model class to add custom query methods:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const UserModelBase = BasicDataModel(User, "users");
|
|
145
|
+
|
|
146
|
+
class CustomUserModel extends UserModelBase {
|
|
147
|
+
async findActive() {
|
|
148
|
+
return this.table
|
|
149
|
+
.filter((u) => u.key("status").eq("active"))
|
|
150
|
+
.then((rows) => rows.map(CustomUserModel.fromDatabase) as User[]);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async register(email: string, name: string) {
|
|
154
|
+
const existing = await this.getBy("email", email);
|
|
155
|
+
if (existing.length > 0) {
|
|
156
|
+
throw new Error("Email already in use");
|
|
157
|
+
}
|
|
158
|
+
return this.insert({ email, name, status: "active" });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## GetModel
|
|
164
|
+
|
|
165
|
+
The `GetModel` function retrieves or creates a cached model instance for a given schema:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { GetModel } from "@antelopejs/interface-database-decorators";
|
|
169
|
+
|
|
170
|
+
const userModel = GetModel(UserModel, "instance-id");
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Model instances are cached by class and instance ID, so repeated calls with the same arguments return the same instance. The schema is resolved automatically from the `schemaName` property on the model class.
|
|
174
|
+
|
|
175
|
+
## Modifier Integration
|
|
176
|
+
|
|
177
|
+
Data models automatically apply all modifier transformations:
|
|
178
|
+
|
|
179
|
+
- **On insert** - `fromPlainData` conversion is applied, the `insert` event fires, and the result is converted with `toDatabase` before writing.
|
|
180
|
+
- **On update** - The same conversion pipeline applies with the `update` event.
|
|
181
|
+
- **On read** - Results from `get`, `getBy`, and `getAll` are converted with `fromDatabase`.
|
|
182
|
+
|
|
183
|
+
This means encrypted fields are encrypted before storage and decrypted on retrieval, hashed fields are hashed on write, and localized fields follow their configured locale behavior -- all transparently.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Parameter Decoration
|
|
2
|
+
|
|
3
|
+
The Database Decorators package provides the `Model` decorator for injecting data model instances into API controller methods. This decorator integrates with the `@antelopejs/interface-api` package's parameter provider system.
|
|
4
|
+
|
|
5
|
+
## The Model Decorator
|
|
6
|
+
|
|
7
|
+
The `Model` decorator injects a cached model instance. It can be used as either a parameter decorator or a property decorator.
|
|
8
|
+
|
|
9
|
+
### Static Instance ID
|
|
10
|
+
|
|
11
|
+
Pass a fixed instance ID to bind the model to a specific schema instance:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Controller, Get } from "@antelopejs/interface-api";
|
|
15
|
+
import { Model } from "@antelopejs/interface-database-decorators";
|
|
16
|
+
import { UserModel } from "./user.model";
|
|
17
|
+
|
|
18
|
+
class UsersController extends Controller("/users") {
|
|
19
|
+
@Get()
|
|
20
|
+
async listUsers(@Model(UserModel, "main") userModel: InstanceType<typeof UserModel>) {
|
|
21
|
+
return await userModel.getAll();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Dynamic Instance ID
|
|
27
|
+
|
|
28
|
+
Pass a callback function to determine the instance ID at request time. The callback receives the request context:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { Controller, Get, Parameter } from "@antelopejs/interface-api";
|
|
32
|
+
import { Model } from "@antelopejs/interface-database-decorators";
|
|
33
|
+
import { UserModel } from "./user.model";
|
|
34
|
+
|
|
35
|
+
class MultitenantController extends Controller("/tenants") {
|
|
36
|
+
@Get("/:tenantId/users")
|
|
37
|
+
async getTenantUsers(
|
|
38
|
+
@Parameter("tenantId") tenantId: string,
|
|
39
|
+
@Model(UserModel, (ctx) => `tenant-${ctx.params.tenantId}`) userModel: InstanceType<typeof UserModel>,
|
|
40
|
+
) {
|
|
41
|
+
return await userModel.getAll();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Property Decoration
|
|
47
|
+
|
|
48
|
+
The `Model` decorator also works as a property decorator. The model is initialized when the controller handles a request:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { Controller, Get, Parameter } from "@antelopejs/interface-api";
|
|
52
|
+
import { Model } from "@antelopejs/interface-database-decorators";
|
|
53
|
+
import { UserModel } from "./models";
|
|
54
|
+
|
|
55
|
+
class UsersController extends Controller("/users") {
|
|
56
|
+
@Model(UserModel, "main")
|
|
57
|
+
private userModel!: InstanceType<typeof UserModel>;
|
|
58
|
+
|
|
59
|
+
@Get()
|
|
60
|
+
async listUsers() {
|
|
61
|
+
return await this.userModel.getAll();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Get("/:id")
|
|
65
|
+
async getUser(@Parameter("id") id: string) {
|
|
66
|
+
return await this.userModel.get(id);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Model Caching
|
|
72
|
+
|
|
73
|
+
The `Model` decorator uses `GetModel` internally, which caches model instances by class and instance ID. Multiple decorators referencing the same model class and instance ID share a single instance:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { GetModel } from "@antelopejs/interface-database-decorators";
|
|
77
|
+
|
|
78
|
+
// These return the same instance
|
|
79
|
+
const model1 = GetModel(UserModel, "main");
|
|
80
|
+
const model2 = GetModel(UserModel, "main");
|
|
81
|
+
|
|
82
|
+
// This returns a different instance
|
|
83
|
+
const model3 = GetModel(UserModel, "other");
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Combine with API Decorators
|
|
87
|
+
|
|
88
|
+
Model decorators compose naturally with other API decorators for building complete CRUD controllers:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { Controller, Get, Post, Put, Delete, RawBody, Parameter } from "@antelopejs/interface-api";
|
|
92
|
+
import { Model } from "@antelopejs/interface-database-decorators";
|
|
93
|
+
import { UserModel } from "./user.model";
|
|
94
|
+
|
|
95
|
+
class UsersController extends Controller("/users") {
|
|
96
|
+
@Get()
|
|
97
|
+
async list(@Model(UserModel, "main") model: InstanceType<typeof UserModel>) {
|
|
98
|
+
return await model.getAll();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Get("/:id")
|
|
102
|
+
async get(
|
|
103
|
+
@Parameter("id") id: string,
|
|
104
|
+
@Model(UserModel, "main") model: InstanceType<typeof UserModel>,
|
|
105
|
+
) {
|
|
106
|
+
return await model.get(id);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Post()
|
|
110
|
+
async create(
|
|
111
|
+
@RawBody() body: Buffer,
|
|
112
|
+
@Model(UserModel, "main") model: InstanceType<typeof UserModel>,
|
|
113
|
+
) {
|
|
114
|
+
const data = JSON.parse(body.toString());
|
|
115
|
+
return await model.insert(data);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Put("/:id")
|
|
119
|
+
async update(
|
|
120
|
+
@Parameter("id") id: string,
|
|
121
|
+
@RawBody() body: Buffer,
|
|
122
|
+
@Model(UserModel, "main") model: InstanceType<typeof UserModel>,
|
|
123
|
+
) {
|
|
124
|
+
const data = JSON.parse(body.toString());
|
|
125
|
+
return await model.update(id, data);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@Delete("/:id")
|
|
129
|
+
async remove(
|
|
130
|
+
@Parameter("id") id: string,
|
|
131
|
+
@Model(UserModel, "main") model: InstanceType<typeof UserModel>,
|
|
132
|
+
) {
|
|
133
|
+
return await model.delete(id);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Custom Parameter Providers
|
|
139
|
+
|
|
140
|
+
For more complex injection logic, create custom parameter providers using the core API:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { SetParameterProvider } from "@antelopejs/interface-api";
|
|
144
|
+
import { GetModel } from "@antelopejs/interface-database-decorators";
|
|
145
|
+
import { MakeParameterAndPropertyDecorator } from "@antelopejs/interface-core/decorators";
|
|
146
|
+
|
|
147
|
+
const CustomModel = MakeParameterAndPropertyDecorator(
|
|
148
|
+
(target, key, index, modelClass, resolver) => {
|
|
149
|
+
SetParameterProvider(target, key, index, (ctx) => {
|
|
150
|
+
const instanceId = resolver(ctx);
|
|
151
|
+
return GetModel(modelClass, instanceId);
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/interface-database-decorators",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "AntelopeJS interface for database schema decorators and model definitions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"main": "dist/index.js",
|
|
19
19
|
"types": "dist/index.d.ts",
|
|
20
20
|
"files": [
|
|
21
|
-
"dist"
|
|
21
|
+
"dist",
|
|
22
|
+
"docs",
|
|
23
|
+
"skills"
|
|
22
24
|
],
|
|
23
25
|
"exports": {
|
|
24
26
|
".": {
|
|
@@ -49,6 +51,10 @@
|
|
|
49
51
|
"types": "./dist/table.d.ts",
|
|
50
52
|
"default": "./dist/table.js"
|
|
51
53
|
},
|
|
54
|
+
"./modifiers/autodate": {
|
|
55
|
+
"types": "./dist/modifiers/autodate.d.ts",
|
|
56
|
+
"default": "./dist/modifiers/autodate.js"
|
|
57
|
+
},
|
|
52
58
|
"./modifiers/common": {
|
|
53
59
|
"types": "./dist/modifiers/common.d.ts",
|
|
54
60
|
"default": "./dist/modifiers/common.js"
|
|
@@ -125,6 +131,9 @@
|
|
|
125
131
|
"table": [
|
|
126
132
|
"dist/table.d.ts"
|
|
127
133
|
],
|
|
134
|
+
"modifiers/autodate": [
|
|
135
|
+
"dist/modifiers/autodate.d.ts"
|
|
136
|
+
],
|
|
128
137
|
"modifiers/common": [
|
|
129
138
|
"dist/modifiers/common.d.ts"
|
|
130
139
|
],
|
|
@@ -141,6 +150,9 @@
|
|
|
141
150
|
},
|
|
142
151
|
"antelopeJs": {
|
|
143
152
|
"standalone": true,
|
|
144
|
-
"test": "src/antelope.test.ts"
|
|
153
|
+
"test": "src/antelope.test.ts",
|
|
154
|
+
"skills": [
|
|
155
|
+
"./skills"
|
|
156
|
+
]
|
|
145
157
|
}
|
|
146
158
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: database-decorators-interface
|
|
3
|
+
description: Provides decorator-based database table definitions for AntelopeJS - a Table base class with RegisterTable/Index/Field/Fixture/Relation decorators, field modifiers (Encrypted, Hashed, Localized, CreationTime, UpdateTime), BasicDataModel CRUD models with validation, RegisterSchema provisioning, and the Model decorator for injecting models into API controllers. Use when code imports @antelopejs/interface-database-decorators (root or /table, /schema, /database, /model, /relation, /common, /modifiers/* subpaths), or when asked to define a database table or data model class, add encrypted/hashed/localized/timestamp fields, seed fixture data, register a schema, or inject a data model into an interface-api controller.
|
|
4
|
+
category: antelopejs-interface
|
|
5
|
+
tags: [database, decorators, orm, models, antelopejs]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Database Decorators Interface
|
|
9
|
+
|
|
10
|
+
Consumer-side decorator layer on top of `@antelopejs/interface-database` (AQL). This package itself has no proxy
|
|
11
|
+
points and no provider side — you never `ImplementInterface` it. All actual database access flows through
|
|
12
|
+
`Schema`/`Table` from `@antelopejs/interface-database`, which must be implemented by a database module (e.g. MongoDB).
|
|
13
|
+
Peer interfaces required: `@antelopejs/interface-core`, `@antelopejs/interface-api` (only for the `Model` controller decorator), `@antelopejs/interface-database`.
|
|
14
|
+
|
|
15
|
+
## Imports
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Table, Field, Index, Fixture } from "@antelopejs/interface-database-decorators/table";
|
|
19
|
+
import { RegisterTable, getTablesForSchema } from "@antelopejs/interface-database-decorators/schema";
|
|
20
|
+
import { RegisterSchema } from "@antelopejs/interface-database-decorators/database";
|
|
21
|
+
import { BasicDataModel, GetModel, Model } from "@antelopejs/interface-database-decorators/model";
|
|
22
|
+
import { Relation } from "@antelopejs/interface-database-decorators/relation";
|
|
23
|
+
import { DatumStaticMetadata, getMetadata } from "@antelopejs/interface-database-decorators/common";
|
|
24
|
+
import { CreationTime, UpdateTime } from "@antelopejs/interface-database-decorators/modifiers/autodate";
|
|
25
|
+
import { Encrypted, EncryptionModifier } from "@antelopejs/interface-database-decorators/modifiers/encryption";
|
|
26
|
+
import { Hashed, HashModifier } from "@antelopejs/interface-database-decorators/modifiers/hash";
|
|
27
|
+
import { Localized, LocalizationModifier } from "@antelopejs/interface-database-decorators/modifiers/localization";
|
|
28
|
+
import { Modifier, OneWayModifier, TwoWayModifier, attachModifier, toPlainData } from "@antelopejs/interface-database-decorators/modifiers/common";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The package root re-exports everything above and also imports `reflect-metadata` (see gotchas).
|
|
32
|
+
|
|
33
|
+
## Minimal usage
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
@RegisterTable("recipes", "cookbook") // (tableName, schemaName)
|
|
37
|
+
class Recipe extends Table {
|
|
38
|
+
// _id: string is inherited as the primary key
|
|
39
|
+
@Index() @Field("string")
|
|
40
|
+
declare slug: string;
|
|
41
|
+
@Field("string")
|
|
42
|
+
declare tagline: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const RecipeModel = BasicDataModel(Recipe); // table name taken from @RegisterTable
|
|
46
|
+
await RegisterSchema("cookbook"); // once at startup: provisions all tables registered for "cookbook", runs fixtures
|
|
47
|
+
|
|
48
|
+
const recipes = GetModel(RecipeModel); // cached per (model class, instanceId)
|
|
49
|
+
await recipes.insert({ slug: "grilled-halloumi", tagline: "Smoky skewers" }, { validate: true });
|
|
50
|
+
const bySlug = await recipes.getBy("slug", "grilled-halloumi");
|
|
51
|
+
const one = await recipes.get(bySlug[0]._id);
|
|
52
|
+
await recipes.update(one._id, { tagline: "Smoky halloumi skewers" });
|
|
53
|
+
await recipes.delete(one._id);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Controller injection (works as parameter or property decorator; second arg is a static `InstanceId` or a
|
|
57
|
+
`(ctx: RequestContext) => InstanceId | undefined` callback — returning `undefined` falls back to the default schema instance):
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
class RecipesController extends Controller("/recipes") {
|
|
61
|
+
@Get()
|
|
62
|
+
async list(@Model(RecipeModel) recipes: InstanceType<typeof RecipeModel>) {
|
|
63
|
+
return recipes.getAll();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Field modifiers
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
class Chef extends Table.with(HashModifier, EncryptionModifier, LocalizationModifier) {
|
|
72
|
+
@Hashed() declare passphrase: string; // one-way; test via instance.testHash("passphrase", value)
|
|
73
|
+
@Encrypted({ secretKey }) declare supplierCode: string; // transparent encrypt/decrypt (autolock + autounlock)
|
|
74
|
+
@Localized() declare motto: string; // read AND write via instance.localize("en", ["motto"])
|
|
75
|
+
@CreationTime() declare hiredAt: Date; // set on insert, stripped from updates
|
|
76
|
+
@UpdateTime() declare lastActiveAt: Date; // refreshed on insert and update
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Gotchas
|
|
81
|
+
|
|
82
|
+
- Only the package root imports `reflect-metadata`; if you import only subpaths, ensure `reflect-metadata`
|
|
83
|
+
is loaded once before decorators run (e.g. `import "@antelopejs/interface-database-decorators"`).
|
|
84
|
+
- `Hashed`, `Encrypted`, and `Localized` require the matching modifier class in `Table.with(...)`; `CreationTime`/`UpdateTime` are event-only and need no mixin.
|
|
85
|
+
- Modifier ordering matters: `attachModifier` throws if you stack a modifier after a one-way modifier (e.g. anything after `@Hashed` on the same field).
|
|
86
|
+
- Hashed fields cannot be read back or queried directly — only equality-tested with `testHash`.
|
|
87
|
+
- Localized fields must be written through a localized instance (`instance.localize(locale).field = ...`);
|
|
88
|
+
values assigned before `localize()` are held in floating state and silently dropped on insert/update.
|
|
89
|
+
- `@Relation({ to: () => Target })` is declarative metadata only: no foreign-key enforcement, consumed by introspection tooling via `RelationStaticMetadata`.
|
|
90
|
+
- `Model.validate` (and `{ validate: true }` on insert/update) only checks fields whose `@Field` type is an
|
|
91
|
+
io-ts-style codec with `.decode`; plain string field tokens are skipped. Update validates with
|
|
92
|
+
`{ partial: true }` semantics.
|
|
93
|
+
- `update(obj)` without an explicit id asserts that the object carries the primary key (`_id` by default).
|
|
94
|
+
- `@Fixture` data is only inserted by `RegisterSchema` when the table is empty (count === 0).
|
|
95
|
+
- `GetModel` caches by model class + instanceId; the same pair always returns the same instance.
|
|
96
|
+
- Serialization of modified instances to plain JSON goes through `toPlainData` (auto-attached as `toJSON`).
|
|
97
|
+
|
|
98
|
+
Deeper reference: this package's `docs/` chapters — Introduction, Table Definitions, Table Modifiers,
|
|
99
|
+
Data Models, Parameter Decoration — and the shipped `.d.ts` files. Do not duplicate them here.
|