@antelopejs/interface-database-decorators 0.1.5 → 0.1.7

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.
Files changed (54) hide show
  1. package/README.md +0 -2
  2. package/dist/antelope.test.js +4 -3
  3. package/dist/antelope.test.js.map +1 -1
  4. package/dist/common.d.ts +2 -1
  5. package/dist/common.js +5 -1
  6. package/dist/common.js.map +1 -1
  7. package/dist/database.js +12 -8
  8. package/dist/database.js.map +1 -1
  9. package/dist/model.d.ts +2 -2
  10. package/dist/model.js +10 -10
  11. package/dist/model.js.map +1 -1
  12. package/dist/modifiers/autodate.js.map +1 -1
  13. package/dist/modifiers/common.js.map +1 -1
  14. package/dist/modifiers/encryption.js +1 -1
  15. package/dist/modifiers/encryption.js.map +1 -1
  16. package/dist/modifiers/hash.js +1 -1
  17. package/dist/modifiers/hash.js.map +1 -1
  18. package/dist/modifiers/localization.js.map +1 -1
  19. package/dist/relation.js.map +1 -1
  20. package/dist/schema.js.map +1 -1
  21. package/dist/table.d.ts +21 -5
  22. package/dist/table.js +8 -8
  23. package/dist/table.js.map +1 -1
  24. package/dist/tests/common.test.js +1 -1
  25. package/dist/tests/common.test.js.map +1 -1
  26. package/dist/tests/database.test.js +80 -2
  27. package/dist/tests/database.test.js.map +1 -1
  28. package/dist/tests/integration.test.js +4 -4
  29. package/dist/tests/integration.test.js.map +1 -1
  30. package/dist/tests/model.test.js +4 -4
  31. package/dist/tests/model.test.js.map +1 -1
  32. package/dist/tests/modifiers/autodate.test.js +3 -3
  33. package/dist/tests/modifiers/autodate.test.js.map +1 -1
  34. package/dist/tests/modifiers/common.test.js +2 -2
  35. package/dist/tests/modifiers/common.test.js.map +1 -1
  36. package/dist/tests/modifiers/encryption.test.js +2 -2
  37. package/dist/tests/modifiers/encryption.test.js.map +1 -1
  38. package/dist/tests/modifiers/hash.test.js +2 -2
  39. package/dist/tests/modifiers/hash.test.js.map +1 -1
  40. package/dist/tests/modifiers/localization.test.js +2 -2
  41. package/dist/tests/modifiers/localization.test.js.map +1 -1
  42. package/dist/tests/relation.test.js +2 -2
  43. package/dist/tests/relation.test.js.map +1 -1
  44. package/dist/tests/schema.test.js +2 -2
  45. package/dist/tests/schema.test.js.map +1 -1
  46. package/dist/tests/table.test.js +11 -11
  47. package/dist/tests/table.test.js.map +1 -1
  48. package/docs/1.introduction.md +22 -0
  49. package/docs/2.table-definitions.md +311 -0
  50. package/docs/3.table-modifiers.md +403 -0
  51. package/docs/4.data-models.md +183 -0
  52. package/docs/5.parameter-decoration.md +166 -0
  53. package/package.json +73 -59
  54. package/skills/database-decorators-interface/SKILL.md +143 -0
@@ -0,0 +1,166 @@
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(
21
+ @Model(UserModel, "main") userModel: InstanceType<typeof UserModel>,
22
+ ) {
23
+ return await userModel.getAll();
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### Dynamic Instance ID
29
+
30
+ Pass a callback function to determine the instance ID at request time. The callback receives the request context:
31
+
32
+ ```typescript
33
+ import { Controller, Get, Parameter } from "@antelopejs/interface-api";
34
+ import { Model } from "@antelopejs/interface-database-decorators";
35
+ import { UserModel } from "./user.model";
36
+
37
+ class MultitenantController extends Controller("/tenants") {
38
+ @Get("/:tenantId/users")
39
+ async getTenantUsers(
40
+ @Parameter("tenantId") tenantId: string,
41
+ @Model(UserModel, (ctx) => `tenant-${ctx.params.tenantId}`)
42
+ userModel: InstanceType<typeof UserModel>,
43
+ ) {
44
+ return await userModel.getAll();
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Property Decoration
50
+
51
+ The `Model` decorator also works as a property decorator. The model is initialized when the controller handles a request:
52
+
53
+ ```typescript
54
+ import { Controller, Get, Parameter } from "@antelopejs/interface-api";
55
+ import { Model } from "@antelopejs/interface-database-decorators";
56
+ import { UserModel } from "./models";
57
+
58
+ class UsersController extends Controller("/users") {
59
+ @Model(UserModel, "main")
60
+ private userModel!: InstanceType<typeof UserModel>;
61
+
62
+ @Get()
63
+ async listUsers() {
64
+ return await this.userModel.getAll();
65
+ }
66
+
67
+ @Get("/:id")
68
+ async getUser(@Parameter("id") id: string) {
69
+ return await this.userModel.get(id);
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## Model Caching
75
+
76
+ 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:
77
+
78
+ ```typescript
79
+ import { GetModel } from "@antelopejs/interface-database-decorators";
80
+
81
+ // These return the same instance
82
+ const model1 = GetModel(UserModel, "main");
83
+ const model2 = GetModel(UserModel, "main");
84
+
85
+ // This returns a different instance
86
+ const model3 = GetModel(UserModel, "other");
87
+ ```
88
+
89
+ ## Combine with API Decorators
90
+
91
+ Model decorators compose naturally with other API decorators for building complete CRUD controllers:
92
+
93
+ ```typescript
94
+ import {
95
+ Controller,
96
+ Get,
97
+ Post,
98
+ Put,
99
+ Delete,
100
+ RawBody,
101
+ Parameter,
102
+ } from "@antelopejs/interface-api";
103
+ import { Model } from "@antelopejs/interface-database-decorators";
104
+ import { UserModel } from "./user.model";
105
+
106
+ class UsersController extends Controller("/users") {
107
+ @Get()
108
+ async list(@Model(UserModel, "main") model: InstanceType<typeof UserModel>) {
109
+ return await model.getAll();
110
+ }
111
+
112
+ @Get("/:id")
113
+ async get(
114
+ @Parameter("id") id: string,
115
+ @Model(UserModel, "main") model: InstanceType<typeof UserModel>,
116
+ ) {
117
+ return await model.get(id);
118
+ }
119
+
120
+ @Post()
121
+ async create(
122
+ @RawBody() body: Buffer,
123
+ @Model(UserModel, "main") model: InstanceType<typeof UserModel>,
124
+ ) {
125
+ const data = JSON.parse(body.toString());
126
+ return await model.insert(data);
127
+ }
128
+
129
+ @Put("/:id")
130
+ async update(
131
+ @Parameter("id") id: string,
132
+ @RawBody() body: Buffer,
133
+ @Model(UserModel, "main") model: InstanceType<typeof UserModel>,
134
+ ) {
135
+ const data = JSON.parse(body.toString());
136
+ return await model.update(id, data);
137
+ }
138
+
139
+ @Delete("/:id")
140
+ async remove(
141
+ @Parameter("id") id: string,
142
+ @Model(UserModel, "main") model: InstanceType<typeof UserModel>,
143
+ ) {
144
+ return await model.delete(id);
145
+ }
146
+ }
147
+ ```
148
+
149
+ ## Custom Parameter Providers
150
+
151
+ For more complex injection logic, create custom parameter providers using the core API:
152
+
153
+ ```typescript
154
+ import { SetParameterProvider } from "@antelopejs/interface-api";
155
+ import { GetModel } from "@antelopejs/interface-database-decorators";
156
+ import { MakeParameterAndPropertyDecorator } from "@antelopejs/interface-core/decorators";
157
+
158
+ const CustomModel = MakeParameterAndPropertyDecorator(
159
+ (target, key, index, modelClass, resolver) => {
160
+ SetParameterProvider(target, key, index, (ctx) => {
161
+ const instanceId = resolver(ctx);
162
+ return GetModel(modelClass, instanceId);
163
+ });
164
+ },
165
+ );
166
+ ```
package/package.json CHANGED
@@ -1,25 +1,63 @@
1
1
  {
2
2
  "name": "@antelopejs/interface-database-decorators",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "AntelopeJS interface for database schema decorators and model definitions",
5
5
  "keywords": [
6
6
  "antelopejs",
7
- "interface",
8
7
  "database",
9
8
  "decorators",
9
+ "interface",
10
10
  "orm"
11
11
  ],
12
+ "license": "Apache-2.0",
12
13
  "repository": {
13
14
  "type": "git",
14
15
  "url": "git+https://github.com/AntelopeJS/interface-database-decorators.git"
15
16
  },
16
- "license": "Apache-2.0",
17
- "packageManager": "pnpm@10.6.5",
18
- "main": "dist/index.js",
19
- "types": "dist/index.d.ts",
20
17
  "files": [
21
- "dist"
18
+ "dist",
19
+ "docs",
20
+ "skills"
22
21
  ],
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "typesVersions": {
25
+ "*": {
26
+ "common": [
27
+ "dist/common.d.ts"
28
+ ],
29
+ "database": [
30
+ "dist/database.d.ts"
31
+ ],
32
+ "model": [
33
+ "dist/model.d.ts"
34
+ ],
35
+ "relation": [
36
+ "dist/relation.d.ts"
37
+ ],
38
+ "schema": [
39
+ "dist/schema.d.ts"
40
+ ],
41
+ "table": [
42
+ "dist/table.d.ts"
43
+ ],
44
+ "modifiers/autodate": [
45
+ "dist/modifiers/autodate.d.ts"
46
+ ],
47
+ "modifiers/common": [
48
+ "dist/modifiers/common.d.ts"
49
+ ],
50
+ "modifiers/encryption": [
51
+ "dist/modifiers/encryption.d.ts"
52
+ ],
53
+ "modifiers/hash": [
54
+ "dist/modifiers/hash.d.ts"
55
+ ],
56
+ "modifiers/localization": [
57
+ "dist/modifiers/localization.d.ts"
58
+ ]
59
+ }
60
+ },
23
61
  "exports": {
24
62
  ".": {
25
63
  "types": "./dist/index.d.ts",
@@ -71,83 +109,59 @@
71
109
  },
72
110
  "./package.json": "./package.json"
73
111
  },
112
+ "publishConfig": {
113
+ "access": "public",
114
+ "provenance": true
115
+ },
74
116
  "scripts": {
75
117
  "build": "rimraf dist && tsc",
76
- "format": "biome format --write .",
77
- "lint:fix": "biome check --write .",
78
- "lint": "biome check .",
118
+ "format": "oxfmt .",
119
+ "lint:fix": "oxlint --fix",
120
+ "lint": "oxlint",
79
121
  "prepack": "pnpm run build",
80
122
  "release": "pnpm run lint && pnpm run prepack && release-it",
81
- "test": "pnpm run build && ajs module test ."
123
+ "test": "pnpm run build && ajs module test .",
124
+ "format:check": "oxfmt --check .",
125
+ "knip": "knip"
82
126
  },
83
127
  "dependencies": {
84
128
  "randomstring": "^1.3.1",
85
129
  "reflect-metadata": "^0.2.2"
86
130
  },
87
131
  "devDependencies": {
88
- "@antelopejs/interface-api": "^0.0.5",
89
- "@antelopejs/interface-core": "^0.0.5",
90
- "@antelopejs/interface-database": "^0.1.3",
91
- "@biomejs/biome": "2.3.2",
132
+ "@antelopejs/core": "^1.9.1",
133
+ "@antelopejs/interface-api": "^0.0.13",
134
+ "@antelopejs/interface-core": "^0.1.1",
135
+ "@antelopejs/interface-database": "^0.1.8",
136
+ "@antelopejs/tooling-configs": "^0.0.6",
92
137
  "@types/chai": "^4.3.20",
93
138
  "@types/mocha": "^10.0.10",
94
139
  "@types/node": "^22.19.15",
95
140
  "@types/randomstring": "^1.3.0",
96
141
  "@types/semver": "^7.7.1",
97
142
  "chai": "^4.5.0",
143
+ "eslint-plugin-perfectionist": "^5.11.0",
144
+ "knip": "^6.34.0",
98
145
  "mongodb-memory-server-core": "^10.4.3",
146
+ "oxfmt": "^0.66.0",
147
+ "oxlint": "1.81.0",
148
+ "oxlint-tsgolint": "^7.0.2001",
99
149
  "release-it": "^19.2.4",
100
150
  "release-it-changelogen": "^0.1.0",
101
151
  "rimraf": "^6.1.3",
102
152
  "typescript": "^5.9.3"
103
153
  },
104
154
  "peerDependencies": {
105
- "@antelopejs/interface-api": ">=0.0.3 <1.0.0",
106
- "@antelopejs/interface-core": ">=0.0.3 <1.0.0",
107
- "@antelopejs/interface-database": ">=0.1.1 <1.0.0"
108
- },
109
- "publishConfig": {
110
- "access": "public"
111
- },
112
- "typesVersions": {
113
- "*": {
114
- "common": [
115
- "dist/common.d.ts"
116
- ],
117
- "database": [
118
- "dist/database.d.ts"
119
- ],
120
- "model": [
121
- "dist/model.d.ts"
122
- ],
123
- "relation": [
124
- "dist/relation.d.ts"
125
- ],
126
- "schema": [
127
- "dist/schema.d.ts"
128
- ],
129
- "table": [
130
- "dist/table.d.ts"
131
- ],
132
- "modifiers/autodate": [
133
- "dist/modifiers/autodate.d.ts"
134
- ],
135
- "modifiers/common": [
136
- "dist/modifiers/common.d.ts"
137
- ],
138
- "modifiers/encryption": [
139
- "dist/modifiers/encryption.d.ts"
140
- ],
141
- "modifiers/hash": [
142
- "dist/modifiers/hash.d.ts"
143
- ],
144
- "modifiers/localization": [
145
- "dist/modifiers/localization.d.ts"
146
- ]
147
- }
155
+ "@antelopejs/interface-api": ">=0.0.13 <1.0.0",
156
+ "@antelopejs/interface-core": ">=0.1.1 <1.0.0",
157
+ "@antelopejs/interface-database": ">=0.1.8 <1.0.0"
148
158
  },
159
+ "packageManager": "pnpm@10.6.5",
149
160
  "antelopeJs": {
150
161
  "standalone": true,
151
- "test": "src/antelope.test.ts"
162
+ "test": "src/antelope.test.ts",
163
+ "skills": [
164
+ "./skills"
165
+ ]
152
166
  }
153
167
  }
@@ -0,0 +1,143 @@
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 {
19
+ Table,
20
+ Field,
21
+ Index,
22
+ Fixture,
23
+ } from "@antelopejs/interface-database-decorators/table";
24
+ import {
25
+ RegisterTable,
26
+ getTablesForSchema,
27
+ } from "@antelopejs/interface-database-decorators/schema";
28
+ import { RegisterSchema } from "@antelopejs/interface-database-decorators/database";
29
+ import {
30
+ BasicDataModel,
31
+ GetModel,
32
+ Model,
33
+ } from "@antelopejs/interface-database-decorators/model";
34
+ import { Relation } from "@antelopejs/interface-database-decorators/relation";
35
+ import {
36
+ DatumStaticMetadata,
37
+ getMetadata,
38
+ } from "@antelopejs/interface-database-decorators/common";
39
+ import {
40
+ CreationTime,
41
+ UpdateTime,
42
+ } from "@antelopejs/interface-database-decorators/modifiers/autodate";
43
+ import {
44
+ Encrypted,
45
+ EncryptionModifier,
46
+ } from "@antelopejs/interface-database-decorators/modifiers/encryption";
47
+ import {
48
+ Hashed,
49
+ HashModifier,
50
+ } from "@antelopejs/interface-database-decorators/modifiers/hash";
51
+ import {
52
+ Localized,
53
+ LocalizationModifier,
54
+ } from "@antelopejs/interface-database-decorators/modifiers/localization";
55
+ import {
56
+ Modifier,
57
+ OneWayModifier,
58
+ TwoWayModifier,
59
+ attachModifier,
60
+ toPlainData,
61
+ } from "@antelopejs/interface-database-decorators/modifiers/common";
62
+ ```
63
+
64
+ The package root re-exports everything above and also imports `reflect-metadata` (see gotchas).
65
+
66
+ ## Minimal usage
67
+
68
+ ```typescript
69
+ @RegisterTable("recipes", "cookbook") // (tableName, schemaName)
70
+ class Recipe extends Table {
71
+ // _id: string is inherited as the primary key
72
+ @Index()
73
+ @Field("string")
74
+ declare slug: string;
75
+ @Field("string")
76
+ declare tagline: string;
77
+ }
78
+
79
+ const RecipeModel = BasicDataModel(Recipe); // table name taken from @RegisterTable
80
+ await RegisterSchema("cookbook"); // once at startup: provisions all tables registered for "cookbook", runs fixtures
81
+
82
+ const recipes = GetModel(RecipeModel); // cached per (model class, instanceId)
83
+ await recipes.insert(
84
+ { slug: "grilled-halloumi", tagline: "Smoky skewers" },
85
+ { validate: true },
86
+ );
87
+ const bySlug = await recipes.getBy("slug", "grilled-halloumi");
88
+ const one = await recipes.get(bySlug[0]._id);
89
+ await recipes.update(one._id, { tagline: "Smoky halloumi skewers" });
90
+ await recipes.delete(one._id);
91
+ ```
92
+
93
+ Controller injection (works as parameter or property decorator; second arg is a static `InstanceId` or a
94
+ `(ctx: RequestContext) => InstanceId | undefined` callback — returning `undefined` falls back to the default schema instance):
95
+
96
+ ```typescript
97
+ class RecipesController extends Controller("/recipes") {
98
+ @Get()
99
+ async list(@Model(RecipeModel) recipes: InstanceType<typeof RecipeModel>) {
100
+ return recipes.getAll();
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Field modifiers
106
+
107
+ ```typescript
108
+ class Chef extends Table.with(
109
+ HashModifier,
110
+ EncryptionModifier,
111
+ LocalizationModifier,
112
+ ) {
113
+ @Hashed() declare passphrase: string; // one-way; test via instance.testHash("passphrase", value)
114
+ @Encrypted({ secretKey }) declare supplierCode: string; // transparent encrypt/decrypt (autolock + autounlock)
115
+ @Localized() declare motto: string; // read AND write via instance.localize("en", ["motto"])
116
+ @CreationTime() declare hiredAt: Date; // set on insert, stripped from updates
117
+ @UpdateTime() declare lastActiveAt: Date; // refreshed on insert and update
118
+ }
119
+ ```
120
+
121
+ ## Gotchas
122
+
123
+ - Only the package root imports `reflect-metadata`; if you import only subpaths, ensure `reflect-metadata`
124
+ is loaded once before decorators run (e.g. `import "@antelopejs/interface-database-decorators"`).
125
+ - `Hashed`, `Encrypted`, and `Localized` require the matching modifier class in `Table.with(...)`; `CreationTime`/`UpdateTime` are event-only and need no mixin.
126
+ - Modifier ordering matters: `attachModifier` throws if you stack a modifier after a one-way modifier (e.g. anything after `@Hashed` on the same field).
127
+ - Hashed fields cannot be read back or queried directly — only equality-tested with `testHash`.
128
+ - Localized fields must be written through a localized instance (`instance.localize(locale).field = ...`);
129
+ values assigned before `localize()` are held in floating state and silently dropped on insert/update.
130
+ - `@Relation({ to: () => Target })` is declarative metadata only: no foreign-key enforcement, consumed by introspection tooling via `RelationStaticMetadata`.
131
+ - `Model.validate` (and `{ validate: true }` on insert/update) only checks fields whose `@Field` type is an
132
+ io-ts-style codec with `.decode`; plain string field tokens are skipped. Update validates with
133
+ `{ partial: true }` semantics.
134
+ - `update(obj)` without an explicit id asserts that the object carries the primary key (`_id` by default).
135
+ - Indexes are scoped to the schema instance. Add `@Index({ crossInstance: true })` only for indexes also queried
136
+ across all instances (`CROSS_INSTANCE`); it never changes results, only speed. On a group, one member setting it
137
+ applies it to the whole group.
138
+ - `@Fixture` data is only inserted by `RegisterSchema` when the table is empty (count === 0).
139
+ - `GetModel` caches by model class + instanceId; the same pair always returns the same instance.
140
+ - Serialization of modified instances to plain JSON goes through `toPlainData` (auto-attached as `toJSON`).
141
+
142
+ Deeper reference: this package's `docs/` chapters — Introduction, Table Definitions, Table Modifiers,
143
+ Data Models, Parameter Decoration — and the shipped `.d.ts` files. Do not duplicate them here.