@antelopejs/interface-database-decorators 0.1.9 → 0.1.11
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/README.md +1 -1
- package/dist/antelope.test.js +1 -1
- package/dist/common.d.ts +2 -1
- package/dist/common.js +5 -1
- package/dist/common.js.map +1 -1
- package/dist/database.js +1 -0
- package/dist/database.js.map +1 -1
- package/dist/model.js +29 -26
- package/dist/model.js.map +1 -1
- package/dist/modifiers/common.d.ts +24 -4
- package/dist/modifiers/common.js +92 -114
- package/dist/modifiers/common.js.map +1 -1
- package/dist/modifiers/encryption.js +14 -9
- package/dist/modifiers/encryption.js.map +1 -1
- package/dist/modifiers/internal/kinds.d.ts +12 -0
- package/dist/modifiers/internal/kinds.js +29 -0
- package/dist/modifiers/internal/kinds.js.map +1 -0
- package/dist/modifiers/internal/records.d.ts +29 -0
- package/dist/modifiers/internal/records.js +42 -0
- package/dist/modifiers/internal/records.js.map +1 -0
- package/dist/modifiers/localization.d.ts +19 -1
- package/dist/modifiers/localization.js +41 -6
- package/dist/modifiers/localization.js.map +1 -1
- package/dist/schema.js +1 -2
- package/dist/schema.js.map +1 -1
- package/dist/table.d.ts +8 -0
- package/dist/table.js +21 -6
- package/dist/table.js.map +1 -1
- package/dist/tests/codec_helpers.d.ts +6 -2
- package/dist/tests/codec_helpers.js +18 -14
- package/dist/tests/codec_helpers.js.map +1 -1
- package/dist/tests/common.test.js +38 -46
- package/dist/tests/common.test.js.map +1 -1
- package/dist/tests/cross_instance.test.js +27 -2
- package/dist/tests/cross_instance.test.js.map +1 -1
- package/dist/tests/database.test.js +247 -226
- package/dist/tests/database.test.js.map +1 -1
- package/dist/tests/helpers/other-instance.d.ts +1 -1
- package/dist/tests/helpers/other-instance.js +41 -9
- package/dist/tests/helpers/other-instance.js.map +1 -1
- package/dist/tests/integration.test.js +485 -469
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/model.test.js +293 -308
- package/dist/tests/model.test.js.map +1 -1
- package/dist/tests/modifiers/autodate.test.js +80 -86
- package/dist/tests/modifiers/autodate.test.js.map +1 -1
- package/dist/tests/modifiers/common.test.js +181 -195
- package/dist/tests/modifiers/common.test.js.map +1 -1
- package/dist/tests/modifiers/encryption.test.js +149 -155
- package/dist/tests/modifiers/encryption.test.js.map +1 -1
- package/dist/tests/modifiers/hash.test.js +91 -99
- package/dist/tests/modifiers/hash.test.js.map +1 -1
- package/dist/tests/modifiers/localization.test.js +186 -159
- package/dist/tests/modifiers/localization.test.js.map +1 -1
- package/dist/tests/relation.test.js +92 -97
- package/dist/tests/relation.test.js.map +1 -1
- package/dist/tests/schema.test.js +101 -106
- package/dist/tests/schema.test.js.map +1 -1
- package/dist/tests/table.test.js +124 -132
- package/dist/tests/table.test.js.map +1 -1
- package/docs/1.introduction.md +56 -2
- package/docs/2.table-definitions.md +15 -0
- package/docs/3.table-modifiers.md +10 -4
- package/docs/4.data-models.md +12 -0
- package/docs/5.parameter-decoration.md +4 -0
- package/docs/assets/model-conversion.svg +21 -0
- package/package.json +16 -5
- package/skills/database-decorators-interface/SKILL.md +4 -0
- package/skills/database-decorators-interface/cases.json +71 -0
|
@@ -95,6 +95,8 @@ class User extends Table.with(HashModifier) {
|
|
|
95
95
|
| ----------- | -------- | ---------- | ------------------ |
|
|
96
96
|
| `algorithm` | `string` | `"sha256"` | Hashing algorithm. |
|
|
97
97
|
|
|
98
|
+
`LocalizationModifier.fallbackLocaleOf(TableClass, field)` returns a field's `fallbackLocale`, or `undefined` when the field is not localized or declares none. Generated forms use it to require a value in that locale.
|
|
99
|
+
|
|
98
100
|
#### Mixin Method
|
|
99
101
|
|
|
100
102
|
The `HashModifier` mixin adds a `testHash` method to table instances:
|
|
@@ -175,9 +177,9 @@ translations.name = {
|
|
|
175
177
|
|
|
176
178
|
#### Options
|
|
177
179
|
|
|
178
|
-
| Option | Type | Description
|
|
179
|
-
| ---------------- | -------- |
|
|
180
|
-
| `fallbackLocale` | `string` | Locale to use when the requested locale
|
|
180
|
+
| Option | Type | Description |
|
|
181
|
+
| ---------------- | -------- | --------------------------------------------------------------------- |
|
|
182
|
+
| `fallbackLocale` | `string` | Locale to use when the requested locale has no value or an empty one. |
|
|
181
183
|
|
|
182
184
|
#### Mixin Method
|
|
183
185
|
|
|
@@ -187,7 +189,11 @@ The `LocalizationModifier` mixin adds a `localize` method:
|
|
|
187
189
|
localize(locale: string, fields?: Array<keyof this>): this
|
|
188
190
|
```
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
`localize()` changes the active locale and returns the same object, not a locale-specific copy.
|
|
193
|
+
|
|
194
|
+
- **References share state.** `spanish`, `german`, and `product` above refer to one object. A later `localize()` call changes what all three references read.
|
|
195
|
+
- **Fields can be restricted.** When you supply `fields`, only those fields are unlocked for the selected locale.
|
|
196
|
+
- **Copy values before switching.** Save the values you need before changing locales, and do not share one instance between concurrent locale-specific operations.
|
|
191
197
|
|
|
192
198
|
### AutoDateModifier
|
|
193
199
|
|
package/docs/4.data-models.md
CHANGED
|
@@ -100,6 +100,16 @@ await userModel.delete("user-123");
|
|
|
100
100
|
|
|
101
101
|
Data models include static methods for converting between different data representations:
|
|
102
102
|
|
|
103
|
+
| Starting value | Conversion | Result | Persists data? |
|
|
104
|
+
| ----------------------- | --------------- | ------------------------------------------------- | -------------- |
|
|
105
|
+
| Plain application input | `fromPlainData` | Table instance with input modifier events applied | No |
|
|
106
|
+
| Raw database record | `fromDatabase` | Table instance with read modifier events applied | No |
|
|
107
|
+
| Table instance | `toDatabase` | Plain storage representation | No |
|
|
108
|
+
|
|
109
|
+
Use `insert` or `update` to perform a write; conversion alone does not save anything. Reads through `get`, `getBy`, and `getAll` apply `fromDatabase` for you. A custom query through `model.table` returns AQL results, so apply conversion explicitly when you need model instances.
|
|
110
|
+
|
|
111
|
+
{width="480"}
|
|
112
|
+
|
|
103
113
|
### `fromPlainData(obj)`
|
|
104
114
|
|
|
105
115
|
Convert a plain JavaScript object into a Table class instance. Modifier transformations (like encryption and hashing) are applied during this conversion.
|
|
@@ -152,6 +162,8 @@ await userModel.update("user-123", { name: "Bob" }, { validate: true });
|
|
|
152
162
|
|
|
153
163
|
Extend the generated model class to add custom query methods:
|
|
154
164
|
|
|
165
|
+
The `register` method below checks for an existing email to provide early feedback. It does not guarantee uniqueness under concurrent writes; enforce that constraint in the database where supported and handle conflicts.
|
|
166
|
+
|
|
155
167
|
```typescript
|
|
156
168
|
const UserModelBase = BasicDataModel(User, "users");
|
|
157
169
|
|
|
@@ -29,6 +29,8 @@ class UsersController extends Controller("/users") {
|
|
|
29
29
|
|
|
30
30
|
Pass a callback function to determine the instance ID at request time. The callback receives the request context:
|
|
31
31
|
|
|
32
|
+
Authorize access to the requested tenant before reading or writing through the injected model. The callback selects an instance; a URL parameter alone is not proof that the caller belongs to that tenant.
|
|
33
|
+
|
|
32
34
|
```typescript
|
|
33
35
|
import { Controller, Get, Parameter } from "@antelopejs/interface-api";
|
|
34
36
|
import { Model } from "@antelopejs/interface-database-decorators";
|
|
@@ -90,6 +92,8 @@ const model3 = GetModel(UserModel, "other");
|
|
|
90
92
|
|
|
91
93
|
Model decorators compose naturally with other API decorators for building complete CRUD controllers:
|
|
92
94
|
|
|
95
|
+
This example demonstrates injection, not a complete public API policy. Before exposing writes, reject malformed or non-object JSON, allow only permitted fields, validate their values, and authorize the operation. `JSON.parse` and `@Model` do not provide these checks; string-token `@Field` declarations do not validate input either.
|
|
96
|
+
|
|
93
97
|
```typescript
|
|
94
98
|
import {
|
|
95
99
|
Controller,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 460" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">Model conversion and database execution</title>
|
|
3
|
+
<desc id="desc">Writing through a model converts plain input, triggers insert or update modifier events, converts to storage, and executes an AQL write. Reading through a model converts a fetched record with fromDatabase. Static conversion functions do not persist data, and model.table returns raw AQL results.</desc>
|
|
4
|
+
<rect width="480" height="460" rx="16" fill="#f5f7fb"/>
|
|
5
|
+
<g font-family="system-ui, sans-serif" fill="#172b45">
|
|
6
|
+
<text x="24" y="36" font-size="20" font-weight="700">Conversion is not persistence</text>
|
|
7
|
+
<rect x="24" y="58" width="432" height="160" rx="10" fill="#e1ecfa"/>
|
|
8
|
+
<text x="42" y="87" font-size="18" font-weight="700">Model write · insert / update</text>
|
|
9
|
+
<text x="42" y="118" font-size="16">Input → fromPlainData</text>
|
|
10
|
+
<text x="42" y="145" font-size="16">→ insert / update modifier events</text>
|
|
11
|
+
<text x="42" y="172" font-size="16">→ toDatabase → storage representation</text>
|
|
12
|
+
<text x="42" y="199" font-size="16">→ AQL write → IDs / modified count</text>
|
|
13
|
+
<rect x="24" y="238" width="432" height="100" rx="10" fill="#d9eee8"/>
|
|
14
|
+
<text x="42" y="267" font-size="18" font-weight="700">Model read · get / getBy / getAll</text>
|
|
15
|
+
<text x="42" y="296" font-size="16">AQL read → fetched record</text>
|
|
16
|
+
<text x="42" y="323" font-size="16">→ fromDatabase → table instance</text>
|
|
17
|
+
<text x="24" y="375" font-size="16" font-weight="700">Taking the direct AQL path?</text>
|
|
18
|
+
<text x="24" y="403" font-size="16">model.table builds queries without model conversion.</text>
|
|
19
|
+
<text x="24" y="431" font-size="16">Apply fromDatabase to raw rows when needed.</text>
|
|
20
|
+
</g>
|
|
21
|
+
</svg>
|
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.11",
|
|
4
4
|
"description": "AntelopeJS decorators for database tables and typed data models, with encrypted, hashed and localized fields",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -127,7 +127,17 @@
|
|
|
127
127
|
"release": "pnpm run lint && pnpm run prepack && release-it",
|
|
128
128
|
"test": "pnpm run build && ajs module test .",
|
|
129
129
|
"format:check": "oxfmt --check .",
|
|
130
|
-
"knip": "knip"
|
|
130
|
+
"knip": "knip",
|
|
131
|
+
"prepare": "if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1; then lefthook install; fi",
|
|
132
|
+
"checks": "antelopejs-checks",
|
|
133
|
+
"size": "antelopejs-check-size",
|
|
134
|
+
"ratchet": "antelopejs-ratchet",
|
|
135
|
+
"sync": "antelopejs-sync",
|
|
136
|
+
"ratchet:update": "antelopejs-ratchet --update",
|
|
137
|
+
"sync:check": "antelopejs-sync --check",
|
|
138
|
+
"typecheck": "tsc --noEmit",
|
|
139
|
+
"suppressions": "antelopejs-suppressions",
|
|
140
|
+
"suppressions:update": "antelopejs-suppressions --update"
|
|
131
141
|
},
|
|
132
142
|
"dependencies": {
|
|
133
143
|
"randomstring": "^1.3.1",
|
|
@@ -137,8 +147,8 @@
|
|
|
137
147
|
"@antelopejs/core": "^1.16.0",
|
|
138
148
|
"@antelopejs/interface-api": "^0.0.15",
|
|
139
149
|
"@antelopejs/interface-core": "^0.2.4",
|
|
140
|
-
"@antelopejs/interface-database": "^0.1.
|
|
141
|
-
"@antelopejs/tooling-configs": "^0.
|
|
150
|
+
"@antelopejs/interface-database": "^0.1.11",
|
|
151
|
+
"@antelopejs/tooling-configs": "^0.2.11",
|
|
142
152
|
"@types/chai": "^4.3.20",
|
|
143
153
|
"@types/mocha": "^10.0.10",
|
|
144
154
|
"@types/node": "^22.19.15",
|
|
@@ -147,6 +157,7 @@
|
|
|
147
157
|
"chai": "^4.5.0",
|
|
148
158
|
"eslint-plugin-perfectionist": "^5.11.0",
|
|
149
159
|
"knip": "^6.34.0",
|
|
160
|
+
"lefthook": "^2.2.2",
|
|
150
161
|
"mongodb-memory-server-core": "^10.4.3",
|
|
151
162
|
"oxfmt": "^0.66.0",
|
|
152
163
|
"oxlint": "1.81.0",
|
|
@@ -159,7 +170,7 @@
|
|
|
159
170
|
"peerDependencies": {
|
|
160
171
|
"@antelopejs/interface-api": ">=0.0.15 <1.0.0",
|
|
161
172
|
"@antelopejs/interface-core": ">=0.2.4 <1.0.0",
|
|
162
|
-
"@antelopejs/interface-database": ">=0.1.
|
|
173
|
+
"@antelopejs/interface-database": ">=0.1.11 <1.0.0"
|
|
163
174
|
},
|
|
164
175
|
"packageManager": "pnpm@10.6.5",
|
|
165
176
|
"antelopeJs": {
|
|
@@ -127,6 +127,7 @@ class Chef extends Table.with(
|
|
|
127
127
|
- Hashed fields cannot be read back or queried directly — only equality-tested with `testHash`.
|
|
128
128
|
- Localized fields must be written through a localized instance (`instance.localize(locale).field = ...`);
|
|
129
129
|
values assigned before `localize()` are held in floating state and silently dropped on insert/update.
|
|
130
|
+
- A localized value `""` is stored as no value: reads and queries in that locale get the `fallbackLocale` value.
|
|
130
131
|
- `@Relation({ to: () => Target })` is declarative metadata only: no foreign-key enforcement, consumed by introspection tooling via `RelationStaticMetadata`.
|
|
131
132
|
- `Model.validate` (and `{ validate: true }` on insert/update) only checks fields whose `@Field` type is an
|
|
132
133
|
io-ts-style codec with `.decode`; plain string field tokens are skipped. Update validates with
|
|
@@ -135,6 +136,9 @@ class Chef extends Table.with(
|
|
|
135
136
|
- Indexes are scoped to the schema instance. Add `@Index({ crossInstance: true })` only for indexes also queried
|
|
136
137
|
across all instances (`CROSS_INSTANCE`); it never changes results, only speed. On a group, one member setting it
|
|
137
138
|
applies it to the whole group.
|
|
139
|
+
- `@Index({ unique: true })` refuses two rows of an instance with the same key: the write throws `DuplicateKeyError`
|
|
140
|
+
(test with `IsDuplicateKeyError` from `@antelopejs/interface-database`). On a group, one member setting it applies it
|
|
141
|
+
to the whole group.
|
|
138
142
|
- `@Fixture` data is only inserted by `RegisterSchema` when the table is empty (count === 0).
|
|
139
143
|
- `GetModel` caches by model class + instanceId; the same pair always returns the same instance.
|
|
140
144
|
- `RegisterSchema` binds each registered table class to the `Schema` it builds; call it in the module that declares the tables. `User.table()`, `User.table(instanceId)` and `User.table(db)` then return the class's AQL table, and `GetModel` builds `BasicDataModel` models on that same schema, even when another module (bound to another database) calls them. An unbound class throws rather than looking a schema up by id.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"cases": [
|
|
4
|
+
{
|
|
5
|
+
"id": "table-relations",
|
|
6
|
+
"area": "framework",
|
|
7
|
+
"style": "clean",
|
|
8
|
+
"prompt": "How do I define relations between tables?",
|
|
9
|
+
"origin": "docs-assistant",
|
|
10
|
+
"expect": {
|
|
11
|
+
"skills": ["database-decorators-interface"],
|
|
12
|
+
"acceptable": ["database-interface"],
|
|
13
|
+
"mustMention": ["Relation"]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "table-relations-casual",
|
|
18
|
+
"area": "framework",
|
|
19
|
+
"style": "casual",
|
|
20
|
+
"prompt": "how do i link two tables together",
|
|
21
|
+
"origin": "docs-assistant",
|
|
22
|
+
"expect": {
|
|
23
|
+
"skills": ["database-decorators-interface"],
|
|
24
|
+
"acceptable": ["database-interface"],
|
|
25
|
+
"mustMention": ["Relation"]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "database-decorators-interface-aql-neighbour",
|
|
30
|
+
"area": "framework",
|
|
31
|
+
"style": "clean",
|
|
32
|
+
"prompt": "Write a query that returns the novels with at least 300 pages, sorted by page count descending, and watch the table for changes.",
|
|
33
|
+
"expect": {
|
|
34
|
+
"skills": ["database-interface"],
|
|
35
|
+
"acceptable": ["antelopejs", "ajs-consume-interface"],
|
|
36
|
+
"avoid": ["database-decorators-interface"]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "database-decorators-interface-modifiers",
|
|
41
|
+
"area": "framework",
|
|
42
|
+
"style": "clean",
|
|
43
|
+
"prompt": "On my user table class, store the password so it can only be verified, encrypt the API key transparently, and keep created and updated timestamps.",
|
|
44
|
+
"expect": {
|
|
45
|
+
"skills": ["database-decorators-interface"],
|
|
46
|
+
"acceptable": [
|
|
47
|
+
"database-interface",
|
|
48
|
+
"antelopejs",
|
|
49
|
+
"ajs-consume-interface"
|
|
50
|
+
],
|
|
51
|
+
"mustMention": [
|
|
52
|
+
"Hashed",
|
|
53
|
+
"Encrypted",
|
|
54
|
+
"HashModifier",
|
|
55
|
+
"EncryptionModifier"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "database-decorators-interface-fixture-casual",
|
|
61
|
+
"area": "framework",
|
|
62
|
+
"style": "casual",
|
|
63
|
+
"prompt": "how do i seed some default rows when the table is empty",
|
|
64
|
+
"expect": {
|
|
65
|
+
"skills": ["database-decorators-interface"],
|
|
66
|
+
"acceptable": ["database-interface", "antelopejs"],
|
|
67
|
+
"mustMention": ["Fixture", "RegisterSchema"]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|