@antelopejs/core 1.15.2 → 1.17.0
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 +56 -71
- package/dist/core/cli/commands/module/init-action.d.ts +3 -2
- package/dist/core/cli/commands/module/init-action.js +44 -12
- package/dist/core/cli/commands/module/init-action.js.map +1 -1
- package/dist/core/cli/commands/project/init-action.js +19 -8
- package/dist/core/cli/commands/project/init-action.js.map +1 -1
- package/dist/core/cli/git-operations.d.ts +6 -0
- package/dist/core/cli/git-operations.js.map +1 -1
- package/dist/core/cli/package-manager.js +6 -0
- package/dist/core/cli/package-manager.js.map +1 -1
- package/dist/core/lifecycle-signals.d.ts +2 -0
- package/dist/core/lifecycle-signals.js +18 -0
- package/dist/core/lifecycle-signals.js.map +1 -0
- package/dist/core/module-lifecycle.js +2 -0
- package/dist/core/module-lifecycle.js.map +1 -1
- package/dist/core/module-manager.js +19 -1
- package/dist/core/module-manager.js.map +1 -1
- package/dist/core/resolution/package-resolution.d.ts +1 -0
- package/dist/core/resolution/package-resolution.js +1 -0
- package/dist/core/resolution/package-resolution.js.map +1 -1
- package/dist/core/resolution/resolver.d.ts +1 -0
- package/dist/core/resolution/resolver.js +27 -22
- package/dist/core/resolution/resolver.js.map +1 -1
- package/dist/core/runtime/module-loading.js +11 -9
- package/dist/core/runtime/module-loading.js.map +1 -1
- package/package.json +15 -4
- package/skills/ajs-author-interface/SKILL.md +75 -0
- package/skills/ajs-cli/SKILL.md +83 -0
- package/skills/ajs-consume-interface/SKILL.md +108 -0
- package/skills/antelopejs/SKILL.md +116 -0
package/package.json
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "TypeScript framework extending hexagonal architecture across your application and its ecosystem through explicit contracts and replaceable modules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
7
|
+
"cli",
|
|
7
8
|
"framework",
|
|
9
|
+
"hexagonal-architecture",
|
|
8
10
|
"modular",
|
|
11
|
+
"ports-and-adapters",
|
|
9
12
|
"typescript"
|
|
10
13
|
],
|
|
14
|
+
"homepage": "https://framework.antelopejs.com",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/AntelopeJS/antelopejs/issues"
|
|
17
|
+
},
|
|
11
18
|
"license": "Apache-2.0",
|
|
12
19
|
"repository": {
|
|
13
20
|
"type": "git",
|
|
@@ -17,7 +24,8 @@
|
|
|
17
24
|
"ajs": "dist/core/cli/index.js"
|
|
18
25
|
},
|
|
19
26
|
"files": [
|
|
20
|
-
"dist"
|
|
27
|
+
"dist",
|
|
28
|
+
"skills"
|
|
21
29
|
],
|
|
22
30
|
"main": "dist/index.js",
|
|
23
31
|
"types": "dist/index.d.ts",
|
|
@@ -54,7 +62,7 @@
|
|
|
54
62
|
"test:embedded": "pnpm build && ts-node test/embedded/run.ts"
|
|
55
63
|
},
|
|
56
64
|
"dependencies": {
|
|
57
|
-
"@antelopejs/interface-core": ">=0.2.
|
|
65
|
+
"@antelopejs/interface-core": ">=0.2.6 <0.3.0",
|
|
58
66
|
"@clack/prompts": "^1.8.1",
|
|
59
67
|
"@types/proper-lockfile": "^4.1.4",
|
|
60
68
|
"commander": "^13.1.0",
|
|
@@ -68,7 +76,7 @@
|
|
|
68
76
|
"typescript": "^5.9.3"
|
|
69
77
|
},
|
|
70
78
|
"devDependencies": {
|
|
71
|
-
"@antelopejs/tooling-configs": "^0.0.
|
|
79
|
+
"@antelopejs/tooling-configs": "^0.0.9",
|
|
72
80
|
"@types/chai": "^4.3.20",
|
|
73
81
|
"@types/mocha": "^10.0.10",
|
|
74
82
|
"@types/node": "^22.19.15",
|
|
@@ -107,6 +115,9 @@
|
|
|
107
115
|
"antelopeJs": {
|
|
108
116
|
"implements": [
|
|
109
117
|
"@antelopejs/interface-core"
|
|
118
|
+
],
|
|
119
|
+
"skills": [
|
|
120
|
+
"./skills"
|
|
110
121
|
]
|
|
111
122
|
}
|
|
112
123
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ajs-author-interface
|
|
3
|
+
description: Guides authoring or versioning an AntelopeJS interface package itself (@antelopejs/interface-*). Use when editing an interface's contract surface, defining a new interface, or deciding whether a change is breaking and which version to bump.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authoring & versioning interfaces
|
|
7
|
+
|
|
8
|
+
An interface is a **versioned contract** shipped as `@<scope>/interface-<name>` (`@antelopejs/…` for the official ones). Its public surface IS the contract: the `exports` subpaths plus the `.d.ts` they point to. Changing that surface is changing the contract — treat it with the discipline of a public API.
|
|
9
|
+
|
|
10
|
+
## Anatomy of an interface package
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
interface-<name>/
|
|
14
|
+
src/index.ts # barrel (named re-exports in practice; AGENTS.md prefers `export * from`)
|
|
15
|
+
src/<area>.ts # contract surface, grouped by concern (one subpath each)
|
|
16
|
+
src/antelope.test.ts # test entry (referenced by package.json `antelopeJs.test`)
|
|
17
|
+
dist/*.d.ts # built contract — what consumers actually type-check against
|
|
18
|
+
docs/ # hand-written guides (numbered files or directories)
|
|
19
|
+
skills/ # package-shipped skill(s) synced to consumers (package.json `antelopeJs.skills` + `files`) — update when the contract changes
|
|
20
|
+
package.json # `exports` (plus `typesVersions` in older packages) define the subpath surface
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The core tells an interface package from a runtime module by `antelopeJs.implements`: absent or `[]` means interface package (depending on it requires a provider), a list means runtime module. Keep it empty in an interface package.
|
|
24
|
+
|
|
25
|
+
An interface can also start **embedded** in the module that implements it (`src/interface/`, exported as `<module>/interface`, with the module listing its own package in `implements`). Extract it to a standalone `interface-<name>` package once a second implementation appears.
|
|
26
|
+
|
|
27
|
+
Each `exports` subpath is an import consumers pin to — add subpaths deliberately, and keep each one cohesive. Inspect any existing interface's shape as a template with the resolver (`/antelopejs:interface <name> [subpath]` runs `show` and `contract` when the antelopejs plugin is installed):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx -y github:AntelopeJS/agent-skills interface show <name>
|
|
31
|
+
npx -y github:AntelopeJS/agent-skills interface contract <name> [subpath]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Declaring contract members
|
|
35
|
+
|
|
36
|
+
At the boundary, the contract is built from core primitives (see `contract core`):
|
|
37
|
+
|
|
38
|
+
- `InterfaceFunction<Signature>()` — a callable the implementing module attaches to and the provider must implement. Its result is always resolved asynchronously (calls queue until a provider attaches), so design these as async/promise-returning — never a signature that implies a synchronous, provider-config-dependent return.
|
|
39
|
+
- `EventProxy` / `RegisteringProxy` — event and registration attachment points across the boundary.
|
|
40
|
+
- Exported `type`/`interface`/`class` — the data shapes that cross the boundary.
|
|
41
|
+
|
|
42
|
+
But an interface is **not limited to proxy points** — it can (and often should) ship real consumer-side code built over them: chainable builders, helpers, validation, convenience classes. The `database` interface is mostly `Table`/`Query`/`Datum` classes whose methods assemble work locally and only hit a proxy (`RunQuery`) at execution. The rule that enables clean attach/detach on reload isn't "no code" — it's that **the only things that actually cross to the provider are the proxy points**; everything else runs in the caller. Keep signatures explicit (no anonymous inline types — define named interfaces). Add TSDoc to every public member; that TSDoc is what consumers read via `contract`.
|
|
43
|
+
|
|
44
|
+
**Interfaces may build on other interfaces.** Importing another `@antelopejs/interface-*` package to compose contracts is expected (e.g. `data-api` builds on `api`, `api-util`, `core`, `database`, and `database-decorators` — as `peerDependencies`). When you do, that dependency's version becomes part of your own compatibility surface — a breaking change there can force a bump here.
|
|
45
|
+
|
|
46
|
+
## Versioning the contract surface
|
|
47
|
+
|
|
48
|
+
Versions are git tags (`v<version>`), cut with release-it (`.release-it.json`, changelog via the release-it-changelogen plugin). Classify every change by its effect on existing consumers:
|
|
49
|
+
|
|
50
|
+
| Change | Effect |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| Add a new optional member, new subpath, new event — existing code keeps compiling & behaving | additive |
|
|
53
|
+
| Remove/rename a member, change a signature, tighten a type, change runtime semantics of an existing call | **breaking** |
|
|
54
|
+
| Doc/comment/internal-only change, no surface delta | none |
|
|
55
|
+
|
|
56
|
+
Verify the surface delta mechanically before tagging. Read the *current* contract with the resolver, and diff the committed source against the previous tag. Note `dist/` is gitignored — only `src/` is in git, so diff `src/` (where the exported declarations and their TSDoc live), not the built `.d.ts`:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx -y github:AntelopeJS/agent-skills interface contract <name> [sub] # inspect the current surface
|
|
60
|
+
git -C <interface-repo> diff <prev-tag> HEAD -- src/ # what changed since the last release
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If the diff removes or changes any existing exported declaration, it's a breaking change. When in doubt, it's breaking.
|
|
64
|
+
|
|
65
|
+
## Shipping breaking changes without breaking consumers
|
|
66
|
+
|
|
67
|
+
Because consumers pin a version, prefer **additive evolution** over mutation:
|
|
68
|
+
|
|
69
|
+
- Add the new member alongside the old; deprecate the old with TSDoc `@deprecated` rather than deleting it in the same release.
|
|
70
|
+
- Reserve a real removal for a deliberate major release, and note it in `CHANGELOG.md`.
|
|
71
|
+
- A provider module must implement the full contract surface of the version it exports — when you add a required member, that's breaking for providers too, not just consumers.
|
|
72
|
+
|
|
73
|
+
## Building & publishing
|
|
74
|
+
|
|
75
|
+
Repo-wide coding conventions: see the **antelopejs** skill (and each repo's `AGENTS.md`). Publishing rebuilds via the `prepack` hook, but build locally too (`pnpm build`) so `dist/*.d.ts` — the contract consumers see — stays current for local tooling.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ajs-cli
|
|
3
|
+
description: Guides running the `ajs` CLI (projects, modules, config, plugins, update). Use when creating, running, building, testing, or managing modules in an AntelopeJS project, listing or updating CLI plugins, when the user asks what an `ajs` command does, or when starting AntelopeJS inside an existing Node.js server (AdonisJS, Express, Fastify) with `createRuntime`, where `ajs project` does not apply. Everything under `ajs dms` belongs to the dms-frontend skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The `ajs` CLI
|
|
7
|
+
|
|
8
|
+
`ajs` is the entry point to the AntelopeJS core (the global `@antelopejs/core` package). Command groups: `project`, `module`, `config`, plus `update` and `plugins`. Run any command with `--help` for current flags and examples; this skill covers the workflows. **Always pnpm**, never npm/yarn, in these projects.
|
|
9
|
+
|
|
10
|
+
## Projects
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
ajs project init <path> # new project: antelope.config.ts + a first module (imported or from a template)
|
|
14
|
+
ajs project dev # download/install modules, load & connect them, run (former name: ajs project run)
|
|
15
|
+
ajs project dev --watch # reload modules when their files change (-w)
|
|
16
|
+
ajs project dev -e production # select an environment of antelope.config.ts (env: ANTELOPEJS_LAUNCH_ENV)
|
|
17
|
+
ajs project dev -i # REPL with the project loaded
|
|
18
|
+
ajs project dev --inspect # Node inspector (default 127.0.0.1:9229)
|
|
19
|
+
ajs project build # download & install modules, check they load, write .antelope/build/build.json
|
|
20
|
+
ajs project start # launch from the build artifact; --refresh-config re-resolves the config at start
|
|
21
|
+
ajs project logging show|set # view/change logging settings
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`dev` resolves and wires the module graph every run; `start` trusts a prior `build`. Use `build` + `start` in production/CI, `dev --watch` while developing. `dev` and `start` accept `-c, --concurrency <number>`. `start --refresh-config` exits with code `3` when the configured module set differs from the build: rebuild then.
|
|
25
|
+
|
|
26
|
+
`init` asks its questions interactively; in scripts and CI answer them with flags (`--name`, `-t/--template`, `--interfaces a,b`, `--pm pnpm`, `--git-init`/`--no-git-init`) or accept defaults with `-y`. Without a terminal, an unanswered question exits with code `2` before anything is written.
|
|
27
|
+
|
|
28
|
+
## Modules in a project
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ajs project modules list # configured modules + their sources (alias: ls; -j for JSON)
|
|
32
|
+
ajs project modules add <modules...> # add & download modules you name (-m, --mode package|git|local|dir; default package = npm)
|
|
33
|
+
ajs project modules remove <modules...> # remove from config (alias: rm; -f, --force)
|
|
34
|
+
ajs project modules update [modules...] # bump npm modules to their latest versions (--dry-run)
|
|
35
|
+
ajs project modules install # add a provider module for every unresolved interface (-y: never ask)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`add` in package mode accepts `<name>@<exact|range|dist-tag>`; without a spec it writes a caret range of the latest version. `add` does no interface resolution.
|
|
39
|
+
|
|
40
|
+
`modules install` (not `add`) fixes **unresolved imports**, and despite its name it installs no npm dependencies: it finds the interfaces no loaded module implements, looks up their implementing modules in the interface repository, and writes the chosen one into `antelope.config.ts`. A single implementer is added without a question; with several it asks, or takes the first with `-y`.
|
|
41
|
+
|
|
42
|
+
Modules declared in `antelope.config.ts` are downloaded into `.antelope/cache` by `dev`, `build`, and `modules add`; a module's own npm dependencies install inside its cache folder.
|
|
43
|
+
|
|
44
|
+
## Embedded mode: no `ajs project`
|
|
45
|
+
|
|
46
|
+
In **embedded** mode the host application (AdonisJS, Express, Fastify…) owns the process and starts the runtime itself with `createRuntime({ projectFolder, modules, uses })` from `@antelopejs/core`, calling `start()` and `stop()` from its own lifecycle (in AdonisJS, a service provider). Recognize it before running any `ajs project` command: **no `antelope.config.*`**, and a host file calls `createRuntime`.
|
|
47
|
+
|
|
48
|
+
- **No `ajs project …`**: `dev`, `build`, `start`, `modules add|remove|install` and `logging` need `antelope.config.ts` and do not apply. `envOverrides` and `environments` do not exist; configure modules in host code.
|
|
49
|
+
- **Modules are listed in code**, as keys of the `modules` map (`'@antelopejs/mongodb': { config: { ... } }`). To add one, `pnpm add` it (modules resolve from the host's `node_modules` or a local path, never `.antelope/cache`), add its key to the map, and restart the host.
|
|
50
|
+
- **No hot reload**: the runtime forces `dev: false`, so modules apply their production behaviour (e.g. `@antelopejs/api` needs `publicBaseUrl` and listens on its own port). In a DMS app, `ajs dms dev` does not work either: build and start the dashboard (the **dms-project** skill).
|
|
51
|
+
- Host code reaches an interface through `runtime.use("<interface package>")`, never a plain `import` of `@antelopejs/interface-*`.
|
|
52
|
+
- `ajs module init` and `ajs module test` still work on module folders.
|
|
53
|
+
|
|
54
|
+
Guide: <https://framework.antelopejs.com/docs/guides/embedded-runtime>.
|
|
55
|
+
|
|
56
|
+
## Authoring a module
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
ajs module init <path> # scaffold a module from a template (same flags as project init: -t, --interfaces, --pm, -y)
|
|
60
|
+
ajs module test [path] # run the module's tests (-f, --file <path>, repeatable)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`ajs module test` reads the module's `antelopeJs.test` entry (e.g. `src/antelope.test.ts`) as a test *config* (`folder`, `setup`, `cleanup`) and runs the test files found there. `init` and `test` are the only `ajs module` subcommands.
|
|
64
|
+
|
|
65
|
+
A module's `package.json` carries an `antelopeJs` block (it can also live in a dedicated `antelope.module.json`). The interface-relevant fields are `implements` (the interface packages this module *provides*, e.g. `["@antelopejs/interface-database"]`) and `test` (the test entry); the others are `defaultConfig`, `configVars` (config variables the module publishes), `baseUrl`, `paths`, `moduleAliases`, and `skills` (the agent skills it ships, see **antelopejs**). Interfaces a module *consumes* are ordinary `dependencies` (or `optionalDependencies`), not a manifest field; see **ajs-consume-interface**.
|
|
66
|
+
|
|
67
|
+
## Plugins
|
|
68
|
+
|
|
69
|
+
Official plugins add command groups. Everything under `ajs dms` (the DMS frontend, `@antelopejs/dms-frontend`) belongs to the **dms-frontend** skill that package ships ([docs](https://dms.antelopejs.com/docs/frontend/cli/overview)). A plugin resolves from the nearest `node_modules/.bin` first, then `PATH`; a project that lists it in its dependencies always runs its own version. `ajs plugins` lists them with version, source, and compatibility; `ajs help dms` shows a plugin's help.
|
|
70
|
+
|
|
71
|
+
## Config
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ajs config show|get|set|reset # CLI settings; the only key is `git` (the interface repository URL)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The interface repository defaults to `https://github.com/AntelopeJS/interfaces.git`; a non-official URL triggers a warning. Override per command with `-g, --git <url>` (env: `ANTELOPEJS_GIT`) on `modules install` and `module init`.
|
|
78
|
+
|
|
79
|
+
## Common flags and output
|
|
80
|
+
|
|
81
|
+
Global: `--verbose[=channels]` (TRACE logs, stack traces, full output of failed commands; env `ANTELOPEJS_VERBOSE`), `-q, --quiet` (results, warnings and errors only), `--no-color`. Per command: `-p, --project <path>` (env `ANTELOPEJS_PROJECT`), `-e, --env` (env `ANTELOPEJS_LAUNCH_ENV`), `-j, --json` on the read commands (`modules list`, `logging show`, `config show|get`, `plugins`). Results go to stdout, progress and errors to stderr. Exit codes: `0` ok, `1` failure, `2` usage error, `3` refreshed config differs from the build, `130` cancelled. When a command misbehaves, re-run it with `--verbose` before guessing.
|
|
82
|
+
|
|
83
|
+
`ajs update` updates a global CLI install and its global plugins (`ajs update dms` for one plugin); plugins installed in a project are updated with pnpm instead.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ajs-consume-interface
|
|
3
|
+
description: Guides consuming or providing an interface from an AntelopeJS module. Use when writing module code that imports and calls an interface or implements one with ImplementInterface, or when interface imports fail to resolve.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Consuming & providing interfaces
|
|
7
|
+
|
|
8
|
+
For the framework mental model, see the **antelopejs** skill. This skill is the consumer/provider workflow.
|
|
9
|
+
|
|
10
|
+
## Before writing any interface call: resolve the contract
|
|
11
|
+
|
|
12
|
+
Do not guess method names or signatures — they are versioned and change. Look them up with the resolver (`/antelopejs:interface <name> [subpath]` runs `show` and `contract` when the antelopejs plugin is installed):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx -y github:AntelopeJS/agent-skills interface show <name> # surface + versions
|
|
16
|
+
npx -y github:AntelopeJS/agent-skills interface contract <name> [sub] # full TSDoc'd contract
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Each `import "@antelopejs/interface-<name>/<sub>"` line in the output is a literal import you can paste.
|
|
20
|
+
|
|
21
|
+
**Package-shipped skills first, for installed interfaces.** Interface packages may ship their own skill (declared via `antelopeJs.skills`), synced into the project's `.claude/skills/` (`.agents/skills/` in Codex) as `<package-short-name>--<skill>` and loaded by dms-ai. For an interface that is already **installed**, prefer that skill plus the installed package's own `dist/*.d.ts` and `docs/` directory — that is exactly the version the project compiles against. Use the resolver for interfaces **not yet installed**, for discovering what exists (`list`), and for comparing versions (`versions`, `show <name>@<version>`).
|
|
22
|
+
|
|
23
|
+
**The one hard rule:** a module never imports another module's files — only interfaces. Every cross-module link is an attachment onto an interface proxy point, so the core can detach and reattach it on reload. If you're tempted to `import` something from a sibling module, the thing you need belongs in an interface instead.
|
|
24
|
+
|
|
25
|
+
## Importing an interface in a module
|
|
26
|
+
|
|
27
|
+
1. Add the interface package to the module's `dependencies` (`pnpm add @antelopejs/interface-<name>`). Consuming an interface is just depending on its package.
|
|
28
|
+
2. Import the symbols and call them, exactly as `contract`/`show` reported. `InterfaceFunction` calls made before a provider attaches are queued by the proxy layer, so no defensive "not ready yet" checks — but for the same reason every such call is **async**: `await` it, never read it as a synchronous value at module-load top level. (`RegisteringProxy.register` is synchronous and also queues; `EventProxy.emit` is synchronous and does *not* queue — an emit before attach reaches nobody.) Queuing assumes a provider *will* attach: a required dependency with no provider fails startup first (see *Resolving unresolved imports*); one that may legitimately lack a provider is an *optional* interface (see *Optional interfaces*).
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { Table } from "@antelopejs/interface-database/selection";
|
|
32
|
+
// ...use the contract exactly as `contract` reported it
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A consumed interface only *resolves* at runtime if some loaded module in the project provides it — otherwise it's an unresolved import (see below).
|
|
36
|
+
|
|
37
|
+
## Providing (implementing) an interface
|
|
38
|
+
|
|
39
|
+
A module that *provides* an interface declares its package name under `antelopeJs.implements` in `package.json` and calls `ImplementInterface`:
|
|
40
|
+
|
|
41
|
+
```jsonc
|
|
42
|
+
// package.json
|
|
43
|
+
"antelopeJs": { "implements": ["@antelopejs/interface-database"] }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { ImplementInterface } from "@antelopejs/interface-core";
|
|
48
|
+
import * as DBInterface from "@antelopejs/interface-database";
|
|
49
|
+
|
|
50
|
+
ImplementInterface(DBInterface, {
|
|
51
|
+
// implement every InterfaceFunction the contract declares — check with `contract`
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The proxy layer attaches the implementation on module load and detaches on unload/hot-reload — implement idempotently and clean up resources in the module's shutdown lifecycle.
|
|
56
|
+
|
|
57
|
+
## Resolving unresolved imports
|
|
58
|
+
|
|
59
|
+
An interface in a module's `dependencies` is **required**: if no loaded module provides it, the runtime aborts startup with `Unresolved interface dependencies: …` rather than running in a half-wired state. When `ajs` reports unresolved imports, a module needs an interface that no loaded module provides. Run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
ajs project modules install # adds a provider module for every unresolved interface
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`install` looks up each unresolved interface in the interface repository and adds an implementing module to `antelope.config.ts`: a single implementer is added without a question, several are offered as a choice (`-y` takes the first). (`ajs project modules add <modules...>` only adds modules you name explicitly; it does not resolve imports.) To see candidate providers for an interface:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx -y github:AntelopeJS/agent-skills interface implementations <name>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Optional interfaces
|
|
72
|
+
|
|
73
|
+
To consume an interface that may legitimately have no provider, declare its package under top-level **`optionalDependencies`** in `package.json` instead of `dependencies` (it's the standard npm field, a sibling of `dependencies` — not part of the `antelopeJs` block). Import and call it exactly the same way.
|
|
74
|
+
|
|
75
|
+
The difference is what happens when nothing provides it. A missing **required** dependency aborts startup; a missing **optional** one lets startup proceed and the core *stubs* the interface:
|
|
76
|
+
|
|
77
|
+
- **async calls reject** immediately — `await`ing a stubbed method throws `Interface '<name>' has no provider for this async method; the call was rejected. Load a module that implements it to enable this call.` It does **not** queue or hang.
|
|
78
|
+
- **registering proxies keep queuing** — `register` calls are retained and replay if a provider is loaded later; **event emits reach nobody** while stubbed. Neither throws.
|
|
79
|
+
- a one-time **warning** is logged at startup: `Optional interface '<name>' has no provider; async calls on it will reject, sync usage will no-op.`
|
|
80
|
+
|
|
81
|
+
**Detecting whether an optional interface is implemented:** `GetInterfaceInstances(interfaceID)` (from `@antelopejs/interface-core`) returns the calling module's connections to the providers of an interface it declares in `dependencies` or `optionalDependencies`, so `GetInterfaceInstances(id).length > 0` is a valid guard. `try/catch` around the first `await` works too: the stub rejects deterministically with the message above and never hangs.
|
|
82
|
+
|
|
83
|
+
For a declared dependency, a call is either connected, rejected (stubbed optional), or startup-blocked (unprovided required) — with one caveat: startup only validates manifest-level `implements` lists, so a provider that declares an interface but omits one `InterfaceFunction` from its `ImplementInterface` call leaves that particular method's proxy unattached, and an `await` on it queues indefinitely. If a call hangs while the interface is otherwise connected, suspect a partially-implemented provider.
|
|
84
|
+
|
|
85
|
+
(A related case: an interface package whose own `package.json` sets `antelopeJs.standalone` self-hosts when no module implements it — same stubbing path, but treated as expected, so it's logged at trace level rather than warned. That's a property of the interface package, not of how you depend on it.)
|
|
86
|
+
|
|
87
|
+
## Wiring & overriding in antelope.config
|
|
88
|
+
|
|
89
|
+
Modules are declared in `antelope.config.ts` (authored with `defineConfig` from `@antelopejs/interface-core/config`; read the full shape with `contract core config`): `modules: Record<string, string | AntelopeModuleConfig>` — a plain string is a version shorthand, otherwise `source` is one of `package`, `git`, `local`, `local-folder`. Per-module knobs that matter when consuming interfaces:
|
|
90
|
+
|
|
91
|
+
- `importOverrides` — force a specific source/provider for an interface, either as a list of `{ interface, source, id }` entries or a `{ interface: source }` shorthand map; e.g. to bind a consumer to a particular implementation when several exist.
|
|
92
|
+
- `disabledExports` — stop a module from providing an interface it would otherwise export.
|
|
93
|
+
- `exportPriority` — `{ "<interface package>": <number> }` on a provider: the highest becomes the default provider of that interface; a negative value keeps it from becoming the default by accident.
|
|
94
|
+
- `config` — module-specific configuration passed at load.
|
|
95
|
+
|
|
96
|
+
When several loaded modules implement the same interface, each consumer is bound to one of them: its own `importOverrides` pin, a pin carried down from a module above it, or the **default provider** (highest `exportPriority` for that interface on the provider's module entry, alphabetical on a tie, with a startup warning naming where one pin would settle it). Two paths that carry different bindings to the same module stop startup with an error naming that module: pin the interface there. Several `importOverrides` entries with an `id` give one module several connections; `GetInterfaceInstance(interfaceID, id).path` is what to `require()` for the copy bound to that connection.
|
|
97
|
+
|
|
98
|
+
Config values can reference what another module publishes with `${@<module>.<VAR_NAME>}` (the provider lists the names under `antelopeJs.configVars`), e.g. `apiBaseUrl: "${@api.API_PUBLIC_BASE_URL}"`. `envOverrides` maps environment variables onto config paths, and `environments` holds per-`--env` overrides.
|
|
99
|
+
|
|
100
|
+
## Pinning interface versions
|
|
101
|
+
|
|
102
|
+
Interfaces are semver-tagged, but there is **no runtime version negotiation**: the core wires consumer to provider purely by interface *package name*. It evaluates the interface package once per provider it is bound to (modules bound the same way share one copy), so the proxies a consumer calls belong to its bound provider; your own npm-resolved copy is what you *typecheck* against, not necessarily what executes. Your `dependencies` range therefore governs the compile-time contract, which is exactly why it should match what the provider actually implements. Check what a project resolves vs. what's available:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx -y github:AntelopeJS/agent-skills interface versions <name>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
A consumer should target a contract version it has actually been checked against; when bumping across a breaking change, re-run `contract` and diff the surface (see **ajs-author-interface** for the compatibility rules that govern those bumps).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: antelopejs
|
|
3
|
+
description: Gives the AntelopeJS mental model (a tight core and the `ajs` CLI orchestrating modules that talk only through versioned interfaces) and routes to the right skill. Use when working in an AntelopeJS project, module or interface repository (DMS projects included), or when the user mentions AntelopeJS. Routes to ajs-consume-interface, ajs-author-interface, ajs-cli, the interface contract resolver, and the skills synced from the project's packages.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AntelopeJS
|
|
7
|
+
|
|
8
|
+
AntelopeJS is a modular Node.js framework. Read this overview first, then jump to the specialized skill for the task.
|
|
9
|
+
|
|
10
|
+
## The model in one screen
|
|
11
|
+
|
|
12
|
+
- **Core + CLI (`ajs`)** — a small runtime that loads modules, wires their interfaces together, and manages the dev/build/start lifecycle. Users rarely edit the core; they run it via `ajs` (modules declared in `antelope.config.ts`). It can also run **embedded** as a guest inside a host app that owns the process (`createRuntime` from `@antelopejs/core`, e.g. the AdonisJS DMS template): no `antelope.config.ts`, modules are plain npm dependencies, and host code reaches interfaces through `runtime.use("<interface package>[/subpath]")`, never by importing them.
|
|
13
|
+
- **Modules** — independent units of functionality. A module may import **interfaces**, never another module's files. Its *only* links to the rest of the project are the interface proxy points it attaches to — nothing else crosses the boundary.
|
|
14
|
+
- **Interfaces** — **versioned contracts**, usually published as standalone `@<scope>/interface-<name>` packages (a module may also start with an *embedded* interface it exports itself, e.g. `@acme/broker/interface`, and extract it once a second implementation appears). An interface package contains the **proxy points** (`InterfaceFunction`, `EventProxy`, `RegisteringProxy`, …) that actually cross to the provider, the **types** that pass across — and often substantial **client-side code** built over those proxies. It is not just empty slots: the `database` interface is mostly chainable `Table`/`Query`/`Datum` classes that assemble a query *in the caller's process*, crossing the boundary only when `.run()` invokes the underlying `RunQuery` proxy. The contract is the package's whole TypeScript surface (`exports` subpaths + `.d.ts`), versioned with semver git tags. **Interfaces may depend on other interfaces** (e.g. `data-api` builds on `api`, `api-util`, `core`, `database`, and `database-decorators`) — that composition is allowed and normal. What's forbidden is modules reaching into each other.
|
|
15
|
+
|
|
16
|
+
The golden rule: **the only connections between a module and the rest of the project go through interface proxy points** — never a direct `import` of another module's file. Everything else follows from that.
|
|
17
|
+
|
|
18
|
+
### How a connection is made — and unmade
|
|
19
|
+
|
|
20
|
+
Every module-to-module link is built in exactly two steps:
|
|
21
|
+
|
|
22
|
+
1. **The interface defines proxy points.** `InterfaceFunction`/`EventProxy`/`RegisteringProxy` objects are empty attachment slots — and the *only* things that cross to the provider. (`InterfaceFunction` is a factory over the underlying `AsyncProxy` primitive; all four are exported by `@antelopejs/interface-core`.) The package may wrap them in classes and helpers that run consumer-side (like `Query.run()` calling `RunQuery`), but that code is not the boundary; the proxy is.
|
|
23
|
+
2. **The implementing module requires the interface and attaches its implementation onto those proxies** via `ImplementInterface`. Consumers require the same interface and call through the proxies; the proxy queues calls until an implementation is attached.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// 1. Interface package declares the proxy point (no behavior, just the slot)
|
|
27
|
+
export const GetUser = InterfaceFunction<(id: string) => User>();
|
|
28
|
+
|
|
29
|
+
// 2. Implementing module requires the interface and attaches onto it
|
|
30
|
+
import { ImplementInterface } from "@antelopejs/interface-core";
|
|
31
|
+
import * as UserInterface from "@antelopejs/interface-user";
|
|
32
|
+
ImplementInterface(UserInterface, { GetUser: (id) => db.find(id) });
|
|
33
|
+
|
|
34
|
+
// Consumer requires the same interface and calls through the proxy
|
|
35
|
+
const user = await GetUser("42"); // queued until an implementation attaches
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Because every connection lives on a known proxy and nothing else crosses the boundary, the core can **detach and reattach a module's links freely** — that's exactly what makes a module safe to hot-reload (or unload) at runtime: tear down its proxy attachments, swap the code, reattach. A direct module-to-module `import` would be an invisible, non-detachable link and would break this — which is why it's forbidden.
|
|
39
|
+
|
|
40
|
+
**Caveat — what queues, and what's async.** The three proxy kinds behave differently before a provider attaches:
|
|
41
|
+
|
|
42
|
+
- `InterfaceFunction`/`AsyncProxy` calls **queue** and always return a Promise — nothing that returns a *value* from the provider can be synchronous. This rules out an interface function that *both* depends on a provider's state (e.g. its module config) *and* is callable synchronously at consumer load time; anything that needs a live implementation must be `await`ed.
|
|
43
|
+
- `RegisteringProxy.register` is a **synchronous** call that queues too: registrations made early are replayed when the provider attaches.
|
|
44
|
+
- `EventProxy.emit` is synchronous and does **not** queue — it reaches only the handlers registered at that moment; an emit before attach is simply not seen.
|
|
45
|
+
|
|
46
|
+
## Where to go next
|
|
47
|
+
|
|
48
|
+
| Task | Skill |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| Build a module / app *on* AntelopeJS, import & call an interface, resolve unresolved imports | **ajs-consume-interface** |
|
|
51
|
+
| Define a new interface, add/version a contract, decide a semver bump, ship breaking changes | **ajs-author-interface** |
|
|
52
|
+
| Run, build, scaffold, test via the CLI (`ajs project …`, `ajs module …`) | **ajs-cli** |
|
|
53
|
+
| Run, build or deploy the DMS frontend (`ajs dms …`), write a frontend module | **dms-frontend** (synced from `@antelopejs/dms-frontend`) |
|
|
54
|
+
|
|
55
|
+
## Skills shipped by the project's packages
|
|
56
|
+
|
|
57
|
+
Packages ship their knowledge as skills, declared under `antelopeJs.skills` in their `package.json`;
|
|
58
|
+
this skill and the `ajs-*` skills come from `@antelopejs/core` that way. Two mechanisms load them:
|
|
59
|
+
|
|
60
|
+
- **Coding agents** (Claude Code, Codex): the `antelopejs` plugin, or
|
|
61
|
+
`npx -y github:AntelopeJS/agent-skills sync`, links them as `<package-short-name>--<skill>` into the
|
|
62
|
+
project's `.claude/skills/` or `.agents/skills/`, from the workspace (source checkouts),
|
|
63
|
+
`node_modules`, and `.antelope/cache`. Run `/antelopejs:skills` (anywhere else:
|
|
64
|
+
`npx -y github:AntelopeJS/agent-skills list`) to see which skills are synced and which package
|
|
65
|
+
ships each one.
|
|
66
|
+
- **dms-ai** (the DMS assistant) loads the skills of every loaded module, of their dependencies,
|
|
67
|
+
and of the project's own dependencies by itself.
|
|
68
|
+
|
|
69
|
+
**DMS projects** get the DMS suite this way: `dms` (the DMS router; start there), `dms-project`,
|
|
70
|
+
`dms-module`, `dms-pages`, `dms-auth`, `dms-best-practices` from `@antelopejs/dms`, `dms-frontend`
|
|
71
|
+
(the `ajs dms` CLI and frontend modules) from `@antelopejs/dms-frontend`, plus one skill per
|
|
72
|
+
installed `dms-*` module (e.g. `dms-saas`). For DMS work, invoke `dms` first and follow
|
|
73
|
+
its routing. When those skills are missing, the dependencies are not installed yet: run
|
|
74
|
+
`pnpm install` (and `ajs project modules install` for modules declared in `antelope.config.ts`),
|
|
75
|
+
then `/antelopejs:sync-skills` (anywhere else: `npx -y github:AntelopeJS/agent-skills sync`).
|
|
76
|
+
|
|
77
|
+
## Interface contract resolver (use this instead of guessing)
|
|
78
|
+
|
|
79
|
+
Contracts are versioned data, not memorized facts. Never invent an interface's methods — look them up
|
|
80
|
+
with the resolver (`/antelopejs:interface <name> [subpath]` runs `show` and `contract` when the antelopejs plugin is installed):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# List interfaces published on npm (plus any installed locally)
|
|
84
|
+
npx -y github:AntelopeJS/agent-skills interface list
|
|
85
|
+
|
|
86
|
+
# Inspect one: export subpaths, exported symbols, available versions
|
|
87
|
+
npx -y github:AntelopeJS/agent-skills interface show database
|
|
88
|
+
|
|
89
|
+
# Print the full TSDoc'd contract for a subpath (the source of truth)
|
|
90
|
+
npx -y github:AntelopeJS/agent-skills interface contract database query
|
|
91
|
+
|
|
92
|
+
# A specific published version, and the list of available versions
|
|
93
|
+
npx -y github:AntelopeJS/agent-skills interface show database@<version>
|
|
94
|
+
npx -y github:AntelopeJS/agent-skills interface versions database
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The resolver reads contracts straight from the published `@antelopejs/interface-*` packages on
|
|
98
|
+
**npm** — no local checkout needed; it fetches and caches the package on demand. `<name>` accepts
|
|
99
|
+
`core`, `interface-core`, or `@antelopejs/interface-core`, optionally `@<version>` (default: the
|
|
100
|
+
`latest` dist-tag). Bare names resolve against the public scope plus any scope discovered from
|
|
101
|
+
the current project's dependencies (`@<scope>/interface-*` deps contribute their scope;
|
|
102
|
+
`AJS_INTERFACE_SCOPES` adds more). A locally-present copy (the project's `node_modules`,
|
|
103
|
+
`.antelope/cache`, or `$AJS_INTERFACES_DIR`) is used as an offline fast-path when available.
|
|
104
|
+
Flags: `--remote` (force a fresh npm fetch), `--offline` (local only). `npm` honours `.npmrc`,
|
|
105
|
+
so scopes served from private registries resolve too.
|
|
106
|
+
|
|
107
|
+
It also resolves interfaces exposed as **subpaths of a module package**: an embedded interface
|
|
108
|
+
(`<module>/interface`) or a host package exporting `./interfaces/<group>` (older DMS releases did
|
|
109
|
+
this). Use the full import path (`… show @acme/broker/interface`) or `<host-short-name>:<group>`;
|
|
110
|
+
hosts are discovered from the current project, and `AJS_DMS_PACKAGES` names extras.
|
|
111
|
+
|
|
112
|
+
When asked anything about a specific interface's API (what methods exist, a signature, which version has X), run `contract`/`show` and answer from the output — do not rely on training data, which goes stale as contracts version.
|
|
113
|
+
|
|
114
|
+
## Conventions in AntelopeJS repos
|
|
115
|
+
|
|
116
|
+
These repos enforce (see any repo's `AGENTS.md`): **pnpm only** (never npm/yarn), English-only identifiers, no `switch`/`case` (use maps), no comments — self-documenting code, TSDoc allowed on public APIs — no inline/anonymous types, no magic values, functions ≤ ~40 lines. Index files: `AGENTS.md` prefers `export * from './module'`, though existing barrels mostly use named re-exports — match the file you're in.
|