@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 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 (ADR 0011's amendment). A testing module initialized with no
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>/_*`. The framework's validation is outermost:
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 (register
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
- return PLATFORM_MOUNTERS[platform](adapter, AvProtocol.bind(framework), framework.entrypoint, framework.contracts.client.limits.maxRequestBytes);
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.0",
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.0"
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.0",
35
- "@aventara/testing": "0.1.0-pilot.0",
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",