From 9b5d3fc4eec110347b6cb1e372e7dff7de4aec3a Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Sun, 19 Jul 2026 21:56:05 +0200 Subject: [PATCH 1/5] feat(skills): ship a consumer skill with the package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add skills/-interface/SKILL.md — a lean, source-grounded guide for agents consuming this interface (imports, minimal example, gotchas) — declared via antelopeJs.skills and published through the files array. Consumers receive it automatically: the antelopejs Claude Code plugin syncs package-shipped skills into a project's .claude/skills/, and the cms-ai chatbox loads them at runtime. Content was fact-checked against src/ and docs/ by an adversarial review pass (imports validated against the exports map, examples verified against real signatures). --- package.json | 8 +- skills/database-decorators-interface/SKILL.md | 103 ++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 skills/database-decorators-interface/SKILL.md diff --git a/package.json b/package.json index 7fa040e..165d832 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ - "dist" + "dist", + "skills" ], "exports": { ".": { @@ -148,6 +149,9 @@ }, "antelopeJs": { "standalone": true, - "test": "src/antelope.test.ts" + "test": "src/antelope.test.ts", + "skills": [ + "./skills" + ] } } diff --git a/skills/database-decorators-interface/SKILL.md b/skills/database-decorators-interface/SKILL.md new file mode 100644 index 0000000..ecf1565 --- /dev/null +++ b/skills/database-decorators-interface/SKILL.md @@ -0,0 +1,103 @@ +--- +name: database-decorators-interface +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. +category: antelopejs-interface +tags: [database, decorators, orm, models, antelopejs] +--- + +# Database Decorators Interface + +Consumer-side decorator layer on top of `@antelopejs/interface-database` (AQL). This package itself has no +proxy points and no provider side — you never `ImplementInterface` it. All actual database access flows +through `Schema`/`Table` from `@antelopejs/interface-database`, which must be implemented by a database +module (e.g. MongoDB). Peer interfaces required: `@antelopejs/interface-core`, `@antelopejs/interface-api` +(only for the `Model` controller decorator), `@antelopejs/interface-database`. + +## Imports + +```typescript +import { Table, Field, Index, Fixture } from "@antelopejs/interface-database-decorators/table"; +import { RegisterTable, getTablesForSchema } from "@antelopejs/interface-database-decorators/schema"; +import { RegisterSchema } from "@antelopejs/interface-database-decorators/database"; +import { BasicDataModel, GetModel, Model } from "@antelopejs/interface-database-decorators/model"; +import { Relation } from "@antelopejs/interface-database-decorators/relation"; +import { DatumStaticMetadata, getMetadata } from "@antelopejs/interface-database-decorators/common"; +import { CreationTime, UpdateTime } from "@antelopejs/interface-database-decorators/modifiers/autodate"; +import { Encrypted, EncryptionModifier } from "@antelopejs/interface-database-decorators/modifiers/encryption"; +import { Hashed, HashModifier } from "@antelopejs/interface-database-decorators/modifiers/hash"; +import { Localized, LocalizationModifier } from "@antelopejs/interface-database-decorators/modifiers/localization"; +import { Modifier, OneWayModifier, TwoWayModifier, attachModifier, toPlainData } from "@antelopejs/interface-database-decorators/modifiers/common"; +``` + +The package root re-exports everything above and also imports `reflect-metadata` (see gotchas). + +## Minimal usage + +```typescript +@RegisterTable("users", "app") // (tableName, schemaName) +class User extends Table { + // _id: string is inherited as the primary key + @Index() @Field("string") + declare email: string; + @Field("string") + declare name: string; +} + +const UserModel = BasicDataModel(User); // table name taken from @RegisterTable +await RegisterSchema("app"); // once at startup: provisions all tables registered for "app", runs fixtures + +const users = GetModel(UserModel); // cached per (model class, instanceId) +await users.insert({ email: "a@b.c", name: "Ada" }, { validate: true }); +const byEmail = await users.getBy("email", "a@b.c"); +const one = await users.get(byEmail[0]._id); +await users.update(one._id, { name: "Ada L." }); +await users.delete(one._id); +``` + +Controller injection (works as parameter or property decorator; second arg is a static `InstanceId` or a +`(ctx: RequestContext) => InstanceId` callback): + +```typescript +class UsersController extends Controller("/users") { + @Get() + async list(@Model(UserModel) users: InstanceType) { + return users.getAll(); + } +} +``` + +## Field modifiers + +```typescript +class Account extends Table.with(HashModifier, EncryptionModifier, LocalizationModifier) { + @Hashed() declare password: string; // one-way; test via instance.testHash("password", value) + @Encrypted({ secretKey }) declare ssn: string; // transparent encrypt/decrypt (autolock + autounlock) + @Localized() declare bio: string; // read AND write via instance.localize("en", ["bio"]) + @CreationTime() declare createdAt: Date; // set on insert, stripped from updates + @UpdateTime() declare updatedAt: Date; // refreshed on insert and update +} +``` + +## Gotchas + +- Only the package root imports `reflect-metadata`; if you import only subpaths, ensure `reflect-metadata` + is loaded once before decorators run (e.g. `import "@antelopejs/interface-database-decorators"`). +- `Hashed`, `Encrypted`, and `Localized` require the matching modifier class in `Table.with(...)`; + `CreationTime`/`UpdateTime` are event-only and need no mixin. +- Modifier ordering matters: `attachModifier` throws if you stack a modifier after a one-way modifier + (e.g. anything after `@Hashed` on the same field). +- Hashed fields cannot be read back or queried directly — only equality-tested with `testHash`. +- Localized fields must be written through a localized instance (`instance.localize(locale).field = ...`); + values assigned before `localize()` are held in floating state and silently dropped on insert/update. +- `@Relation({ to: () => Target })` is declarative metadata only: no foreign-key enforcement, consumed by + introspection tooling via `RelationStaticMetadata`. +- `Model.validate` (and `{ validate: true }` on insert/update) only checks fields whose `@Field` type is an + io-ts-style codec with `.decode`; plain string field tokens are skipped. Update validates with + `{ partial: true }` semantics. +- `update(obj)` without an explicit id asserts that the object carries the primary key (`_id` by default). +- `@Fixture` data is only inserted by `RegisterSchema` when the table is empty (count === 0). +- `GetModel` caches by model class + instanceId; the same pair always returns the same instance. +- Serialization of modified instances to plain JSON goes through `toPlainData` (auto-attached as `toJSON`). + +Deeper reference: this package's `docs/` chapters — Introduction, Table Definitions, Table Modifiers, +Data Models, Parameter Decoration — and the shipped `.d.ts` files. Do not duplicate them here. From 96b46ad4d744f9beba5e2135fff23219328448ff Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Sun, 19 Jul 2026 22:14:01 +0200 Subject: [PATCH 2/5] address greptile review feedback (greploop iteration 1) --- skills/database-decorators-interface/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/database-decorators-interface/SKILL.md b/skills/database-decorators-interface/SKILL.md index ecf1565..eaef07a 100644 --- a/skills/database-decorators-interface/SKILL.md +++ b/skills/database-decorators-interface/SKILL.md @@ -55,7 +55,7 @@ await users.delete(one._id); ``` Controller injection (works as parameter or property decorator; second arg is a static `InstanceId` or a -`(ctx: RequestContext) => InstanceId` callback): +`(ctx: RequestContext) => InstanceId | undefined` callback — returning `undefined` falls back to the default schema instance): ```typescript class UsersController extends Controller("/users") { From 82d28bc0cfbd3251d6263234a3d2575d02750be0 Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Sun, 19 Jul 2026 23:32:47 +0200 Subject: [PATCH 3/5] fix(skills): ship docs with the package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 165d832..a1a287f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "types": "dist/index.d.ts", "files": [ "dist", + "docs", "skills" ], "exports": { From b541f8221444974e89df45971aeb26f795ba383d Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Mon, 20 Jul 2026 00:12:46 +0200 Subject: [PATCH 4/5] docs(skills): tighten to write-a-skill form (rewrap under 100 lines) --- skills/database-decorators-interface/SKILL.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/skills/database-decorators-interface/SKILL.md b/skills/database-decorators-interface/SKILL.md index eaef07a..dda55f4 100644 --- a/skills/database-decorators-interface/SKILL.md +++ b/skills/database-decorators-interface/SKILL.md @@ -7,11 +7,10 @@ tags: [database, decorators, orm, models, antelopejs] # Database Decorators Interface -Consumer-side decorator layer on top of `@antelopejs/interface-database` (AQL). This package itself has no -proxy points and no provider side — you never `ImplementInterface` it. All actual database access flows -through `Schema`/`Table` from `@antelopejs/interface-database`, which must be implemented by a database -module (e.g. MongoDB). Peer interfaces required: `@antelopejs/interface-core`, `@antelopejs/interface-api` -(only for the `Model` controller decorator), `@antelopejs/interface-database`. +Consumer-side decorator layer on top of `@antelopejs/interface-database` (AQL). This package itself has no proxy +points and no provider side — you never `ImplementInterface` it. All actual database access flows through +`Schema`/`Table` from `@antelopejs/interface-database`, which must be implemented by a database module (e.g. MongoDB). +Peer interfaces required: `@antelopejs/interface-core`, `@antelopejs/interface-api` (only for the `Model` controller decorator), `@antelopejs/interface-database`. ## Imports @@ -82,15 +81,12 @@ class Account extends Table.with(HashModifier, EncryptionModifier, LocalizationM - Only the package root imports `reflect-metadata`; if you import only subpaths, ensure `reflect-metadata` is loaded once before decorators run (e.g. `import "@antelopejs/interface-database-decorators"`). -- `Hashed`, `Encrypted`, and `Localized` require the matching modifier class in `Table.with(...)`; - `CreationTime`/`UpdateTime` are event-only and need no mixin. -- Modifier ordering matters: `attachModifier` throws if you stack a modifier after a one-way modifier - (e.g. anything after `@Hashed` on the same field). +- `Hashed`, `Encrypted`, and `Localized` require the matching modifier class in `Table.with(...)`; `CreationTime`/`UpdateTime` are event-only and need no mixin. +- Modifier ordering matters: `attachModifier` throws if you stack a modifier after a one-way modifier (e.g. anything after `@Hashed` on the same field). - Hashed fields cannot be read back or queried directly — only equality-tested with `testHash`. - Localized fields must be written through a localized instance (`instance.localize(locale).field = ...`); values assigned before `localize()` are held in floating state and silently dropped on insert/update. -- `@Relation({ to: () => Target })` is declarative metadata only: no foreign-key enforcement, consumed by - introspection tooling via `RelationStaticMetadata`. +- `@Relation({ to: () => Target })` is declarative metadata only: no foreign-key enforcement, consumed by introspection tooling via `RelationStaticMetadata`. - `Model.validate` (and `{ validate: true }` on insert/update) only checks fields whose `@Field` type is an io-ts-style codec with `.decode`; plain string field tokens are skipped. Update validates with `{ partial: true }` semantics. From b3efd64e8ade09316ea876584389192f5f2e8bf5 Mon Sep 17 00:00:00 2001 From: Antony Rizzitelli Date: Mon, 20 Jul 2026 16:12:48 +0200 Subject: [PATCH 5/5] docs(skills): use fictional domains in code examples --- skills/database-decorators-interface/SKILL.md | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/skills/database-decorators-interface/SKILL.md b/skills/database-decorators-interface/SKILL.md index dda55f4..de0aafa 100644 --- a/skills/database-decorators-interface/SKILL.md +++ b/skills/database-decorators-interface/SKILL.md @@ -33,34 +33,34 @@ The package root re-exports everything above and also imports `reflect-metadata` ## Minimal usage ```typescript -@RegisterTable("users", "app") // (tableName, schemaName) -class User extends Table { +@RegisterTable("recipes", "cookbook") // (tableName, schemaName) +class Recipe extends Table { // _id: string is inherited as the primary key @Index() @Field("string") - declare email: string; + declare slug: string; @Field("string") - declare name: string; + declare tagline: string; } -const UserModel = BasicDataModel(User); // table name taken from @RegisterTable -await RegisterSchema("app"); // once at startup: provisions all tables registered for "app", runs fixtures +const RecipeModel = BasicDataModel(Recipe); // table name taken from @RegisterTable +await RegisterSchema("cookbook"); // once at startup: provisions all tables registered for "cookbook", runs fixtures -const users = GetModel(UserModel); // cached per (model class, instanceId) -await users.insert({ email: "a@b.c", name: "Ada" }, { validate: true }); -const byEmail = await users.getBy("email", "a@b.c"); -const one = await users.get(byEmail[0]._id); -await users.update(one._id, { name: "Ada L." }); -await users.delete(one._id); +const recipes = GetModel(RecipeModel); // cached per (model class, instanceId) +await recipes.insert({ slug: "grilled-halloumi", tagline: "Smoky skewers" }, { validate: true }); +const bySlug = await recipes.getBy("slug", "grilled-halloumi"); +const one = await recipes.get(bySlug[0]._id); +await recipes.update(one._id, { tagline: "Smoky halloumi skewers" }); +await recipes.delete(one._id); ``` Controller injection (works as parameter or property decorator; second arg is a static `InstanceId` or a `(ctx: RequestContext) => InstanceId | undefined` callback — returning `undefined` falls back to the default schema instance): ```typescript -class UsersController extends Controller("/users") { +class RecipesController extends Controller("/recipes") { @Get() - async list(@Model(UserModel) users: InstanceType) { - return users.getAll(); + async list(@Model(RecipeModel) recipes: InstanceType) { + return recipes.getAll(); } } ``` @@ -68,12 +68,12 @@ class UsersController extends Controller("/users") { ## Field modifiers ```typescript -class Account extends Table.with(HashModifier, EncryptionModifier, LocalizationModifier) { - @Hashed() declare password: string; // one-way; test via instance.testHash("password", value) - @Encrypted({ secretKey }) declare ssn: string; // transparent encrypt/decrypt (autolock + autounlock) - @Localized() declare bio: string; // read AND write via instance.localize("en", ["bio"]) - @CreationTime() declare createdAt: Date; // set on insert, stripped from updates - @UpdateTime() declare updatedAt: Date; // refreshed on insert and update +class Chef extends Table.with(HashModifier, EncryptionModifier, LocalizationModifier) { + @Hashed() declare passphrase: string; // one-way; test via instance.testHash("passphrase", value) + @Encrypted({ secretKey }) declare supplierCode: string; // transparent encrypt/decrypt (autolock + autounlock) + @Localized() declare motto: string; // read AND write via instance.localize("en", ["motto"]) + @CreationTime() declare hiredAt: Date; // set on insert, stripped from updates + @UpdateTime() declare lastActiveAt: Date; // refreshed on insert and update } ```