@aventara/nest 0.1.0-pilot.0 → 0.1.0-pilot.1
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 +38 -4
- package/dist/protocol-host.factory.d.ts +8 -0
- package/dist/protocol-host.factory.js +28 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -80,18 +80,53 @@ bootstrap error instead of rejecting; pass `abortOnError: false` to `NestFactory
|
|
|
80
80
|
|
|
81
81
|
**Nest's testing module works.** `Test.createTestingModule({ imports: [AppModule] }).compile()` builds the providers
|
|
82
82
|
before any HTTP platform exists; the module waits for the one `createNestApplication()` supplies, and still refuses an
|
|
83
|
-
unsupported platform before the application listens
|
|
83
|
+
unsupported platform before the application listens. A testing module initialized with no
|
|
84
84
|
application at all is refused at `init()`, as a platform of `none`.
|
|
85
85
|
|
|
86
86
|
**Nothing to shut down.** The module owns no resource. Your Prisma provider closes its own client — in its
|
|
87
87
|
`onModuleDestroy`, with `app.enableShutdownHooks()` for signals.
|
|
88
88
|
|
|
89
|
+
## Calling it by hand
|
|
90
|
+
|
|
91
|
+
Against the shape `aventara init` writes — the entrypoint `/api`, a Prisma `User` model:
|
|
92
|
+
|
|
93
|
+
<!-- pilot-gate:curl -->
|
|
94
|
+
```bash
|
|
95
|
+
# 1. The contract. Its protocol.hash is what every resource request sends as Aventara-Contract-Hash.
|
|
96
|
+
HASH=$(curl -s http://localhost:3000/api/_contract | node -p 'JSON.parse(require("fs").readFileSync(0, "utf8")).protocol.hash')
|
|
97
|
+
|
|
98
|
+
# 2. find.many on User, with both identity headers.
|
|
99
|
+
curl -s -X POST http://localhost:3000/api/_resources/User/find/many \
|
|
100
|
+
-H 'Content-Type: application/json' \
|
|
101
|
+
-H 'Aventara-Protocol-Version: 1' \
|
|
102
|
+
-H "Aventara-Contract-Hash: $HASH" \
|
|
103
|
+
-d '{}'
|
|
104
|
+
# {"data":[],"code":"A1000","cause":null}
|
|
105
|
+
```
|
|
106
|
+
<!-- /pilot-gate:curl -->
|
|
107
|
+
|
|
108
|
+
Leave out an identity header and the answer is `400 A2000`, naming the header that is missing. The hash changes
|
|
109
|
+
whenever the schema or the configuration does: fetch it again, and regenerate the client.
|
|
110
|
+
|
|
111
|
+
- **Resource keys are the model names, as written.** `model User` is `User` everywhere: on the wire
|
|
112
|
+
(`/_resources/User/find/many`) and in the generated client (`avClient.User.find.many({})`). Nothing is renamed,
|
|
113
|
+
lower-cased or pluralized.
|
|
114
|
+
- **Results are read-only.** A list result is a `readonly` array: type it `readonly User[]`, not `User[]` —
|
|
115
|
+
`const users: readonly User[] = await avClient.User.find.many({});`.
|
|
116
|
+
|
|
89
117
|
## What runs on a protocol route
|
|
90
118
|
|
|
91
119
|
The protocol's routes are platform routes, registered from the module's `configure()`, one per
|
|
92
120
|
`protocol.surface()` entry — every advertised operation, `GET <entrypoint>/_contract`, and `POST
|
|
93
121
|
<entrypoint>/_transactions` only when the ClientContract advertises `interactive` — plus the absence handler for the
|
|
94
|
-
rest of `<entrypoint>/_*`.
|
|
122
|
+
rest of `<entrypoint>/_*`. Nest's `RouterExplorer` does not list platform routes, so the module logs one line of its
|
|
123
|
+
own when it mounts them, through Nest's `Logger` (context `AventaraModule`; `logger: false` silences it like any other):
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
[Nest] LOG [AventaraModule] Aventara mounted 10 operations at /api (+ GET /api/_contract, POST /api/_transactions)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The framework's validation is outermost:
|
|
95
130
|
|
|
96
131
|
| | Express | Fastify |
|
|
97
132
|
|---|---|---|
|
|
@@ -128,8 +163,7 @@ protocol; set the entrypoint to where you want it, and point the generated clien
|
|
|
128
163
|
- **Express routing is case-insensitive** by default, so it also answers the protocol under, say, `/API/_contract`;
|
|
129
164
|
Fastify's is not. That is Express's own `case sensitive routing` setting, off by default, and yours to change.
|
|
130
165
|
- **Express answers `500 A3000`** to a protocol route requested with a percent-encoding a generated client never sends
|
|
131
|
-
(for example `%6Eotes` for `notes`); Fastify and the in-process protocol execute it. A known divergence
|
|
132
|
-
F-843).
|
|
166
|
+
(for example `%6Eotes` for `notes`); Fastify and the in-process protocol execute it. A known divergence.
|
|
133
167
|
- On Express the response carries Express's `X-Powered-By` header; `app.disable("x-powered-by")` removes it.
|
|
134
168
|
|
|
135
169
|
## License
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Framework } from "@aventara/core";
|
|
2
2
|
import type { AbstractHttpAdapter } from "@nestjs/core";
|
|
3
|
+
import type { SurfaceRoute } from "./protocol-route.table.js";
|
|
3
4
|
/** The protocol, bound once at startup, and the one call that mounts it. */
|
|
4
5
|
export type ProtocolHost = {
|
|
5
6
|
readonly mount: () => void;
|
|
@@ -11,6 +12,13 @@ export type ProtocolHost = {
|
|
|
11
12
|
* routes are mounted later, from the module's `configure()` (Q5).
|
|
12
13
|
*/
|
|
13
14
|
export declare function createProtocolHost(adapter: AbstractHttpAdapter | undefined, framework: Framework): ProtocolHost;
|
|
15
|
+
/**
|
|
16
|
+
* What one mount registered, read off `protocol.surface()`: the operation
|
|
17
|
+
* count under the entrypoint, then each framework route by method and path —
|
|
18
|
+
* e.g. `Aventara mounted 5 operations at /api (+ GET /api/_contract, POST
|
|
19
|
+
* /api/_transactions)`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mountedLine(routes: readonly SurfaceRoute[], entrypoint: string): string;
|
|
14
22
|
/** What `AventaraModule` holds: a host, perhaps waiting for its platform, and the check that it has one. */
|
|
15
23
|
export type PendingProtocolHost = ProtocolHost & {
|
|
16
24
|
/** Throws when no platform arrived, or the one that did is refused. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AvProtocol } from "@aventara/core/protocol";
|
|
2
|
+
import { Logger } from "@nestjs/common";
|
|
2
3
|
import { createExpressProtocolHost } from "./express-protocol.mounter.js";
|
|
3
4
|
import { createFastifyProtocolHost, } from "./fastify-protocol.mounter.js";
|
|
4
5
|
import { checkedPlatform, } from "./host-bootstrap.validator.js";
|
|
@@ -18,7 +19,33 @@ const PLATFORM_MOUNTERS = {
|
|
|
18
19
|
*/
|
|
19
20
|
export function createProtocolHost(adapter, framework) {
|
|
20
21
|
const platform = checkedPlatform(adapter?.getType());
|
|
21
|
-
|
|
22
|
+
const binding = AvProtocol.bind(framework);
|
|
23
|
+
const host = PLATFORM_MOUNTERS[platform](adapter, binding, framework.entrypoint, framework.contracts.client.limits.maxRequestBytes);
|
|
24
|
+
return {
|
|
25
|
+
mount: () => {
|
|
26
|
+
host.mount();
|
|
27
|
+
MOUNT_LOGGER.log(mountedLine(binding.surface(), framework.entrypoint));
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* pilot.1 — the protocol's routes are platform routes, which Nest's
|
|
33
|
+
* `RouterExplorer` never logs; this is the one line that says they exist.
|
|
34
|
+
* Through Nest's `Logger`, so the application's logger settings govern it.
|
|
35
|
+
*/
|
|
36
|
+
const MOUNT_LOGGER = new Logger("AventaraModule");
|
|
37
|
+
/**
|
|
38
|
+
* What one mount registered, read off `protocol.surface()`: the operation
|
|
39
|
+
* count under the entrypoint, then each framework route by method and path —
|
|
40
|
+
* e.g. `Aventara mounted 5 operations at /api (+ GET /api/_contract, POST
|
|
41
|
+
* /api/_transactions)`.
|
|
42
|
+
*/
|
|
43
|
+
export function mountedLine(routes, entrypoint) {
|
|
44
|
+
const operations = routes.filter((route) => route.kind === "operation");
|
|
45
|
+
const framework = routes
|
|
46
|
+
.filter((route) => route.kind !== "operation")
|
|
47
|
+
.map((route) => `${route.method} ${entrypoint}${route.path}`);
|
|
48
|
+
return `Aventara mounted ${operations.length} ${operations.length === 1 ? "operation" : "operations"} at ${entrypoint === "" ? "/" : entrypoint} (+ ${framework.join(", ")})`;
|
|
22
49
|
}
|
|
23
50
|
/**
|
|
24
51
|
* N1 (plan B23) — a host for an application whose HTTP adapter may arrive after
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aventara/nest",
|
|
3
|
-
"version": "0.1.0-pilot.
|
|
3
|
+
"version": "0.1.0-pilot.1",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "NestJS host integration for Aventara.",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"LICENSE-ADDITIONAL-PERMISSION.md"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@aventara/core": "0.1.0-pilot.
|
|
24
|
+
"@aventara/core": "0.1.0-pilot.1"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@nestjs/common": "^12.0.0",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@aventara/prisma7-adapter": "0.1.0-pilot.
|
|
35
|
-
"@aventara/testing": "0.1.0-pilot.
|
|
34
|
+
"@aventara/prisma7-adapter": "0.1.0-pilot.1",
|
|
35
|
+
"@aventara/testing": "0.1.0-pilot.1",
|
|
36
36
|
"@nestjs/common": "^12.1.2",
|
|
37
37
|
"@nestjs/core": "^12.1.2",
|
|
38
38
|
"@nestjs/platform-express": "^12.1.2",
|