Resolve the SDK by version; mesh-controller/foundation rename #9
+23
-17
@@ -1,28 +1,36 @@
|
|||||||
# Two images from one recipe: the one modules are COMPILED in, and the one they RUN in.
|
# Three stages, two published images: the one modules are COMPILED in, and the one they RUN in.
|
||||||
#
|
#
|
||||||
# **They were the same image, and that was a mistake.** A module's recipe starts from this and
|
# **They were the same image, and that was a mistake.** A module's recipe starts from this and
|
||||||
# invokes the compiler out of it, so the compiler had to be here — and because the same image was
|
# invokes the compiler out of it, so the compiler had to be here — and because the same image was
|
||||||
# also what every module ran in, every running container on every machine carried a TypeScript
|
# also what every module ran in, every running container on every machine carried a TypeScript
|
||||||
# compiler it would never invoke. 23 of the 28 MB of libraries were that compiler. It was defended
|
# compiler it would never invoke. The answer is separate stages rather than one image bad at both
|
||||||
# in a comment, which made a workaround look like a decision: the earlier attempt to prune the build
|
# jobs. `build.artifacts` in module.json names the published stage each — `toolchain` and `runtime`.
|
||||||
# tools produced a smaller image that nothing could be built on, and the answer to that is two
|
|
||||||
# images rather than one image that is bad at both jobs.
|
|
||||||
#
|
#
|
||||||
# Kept in one recipe deliberately. They must agree about the operating system, the language version
|
# The `deps` stage is neither published nor named there: it is where the mesh's package registry is
|
||||||
# and the library, and two files drift. `build.artifacts` in module.json names a stage each.
|
# reached, so it is where — and only where — the credential to reach it exists. The toolchain copies
|
||||||
|
# resolved node_modules out of it, so the credential is in no image any machine ever holds
|
||||||
|
# (novox/hq ADR 0076). This is the buildkit-secret's job done without buildkit, because a machine's
|
||||||
|
# docker may carry no buildx.
|
||||||
|
|
||||||
# ---- toolchain: what a module is compiled in -------------------------------------------------
|
# ---- deps: node_modules resolved from the mesh's registry, credential and all ----------------
|
||||||
FROM node:22-bookworm-slim AS toolchain
|
FROM node:22-bookworm-slim AS deps
|
||||||
# git, because a dependency named by a git URL is fetched by git and this image does not carry it.
|
|
||||||
# Only here: what it is needed for happens at build time, and an image that can clone is an image
|
|
||||||
# that can be made to clone.
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends git ca-certificates \
|
&& apt-get install -y --no-install-recommends git ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json ./
|
||||||
# Development dependencies included: the compiler is one, and so is the toolkit's own.
|
# The builder writes .npmrc into the build context; it authenticates to the mesh's package registry
|
||||||
|
# for the @novox scope, which is where @novox/mesh-sdk resolves. This stage is not published, so the
|
||||||
|
# credential travels no further than here. Development dependencies included: the compiler is one.
|
||||||
|
COPY .npmrc ./.npmrc
|
||||||
RUN npm install --no-audit --no-fund
|
RUN npm install --no-audit --no-fund
|
||||||
|
|
||||||
|
# ---- toolchain: what a module is compiled in, WITHOUT the credential --------------------------
|
||||||
|
FROM node:22-bookworm-slim AS toolchain
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json ./
|
||||||
|
# The resolved libraries, but not the .npmrc that resolved them.
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
# The toolkit arrives compiled. It used to arrive as sources, and this compiled it by hand — the
|
# The toolkit arrives compiled. It used to arrive as sources, and this compiled it by hand — the
|
||||||
# hook that builds it on install was running all along, and the result was then packed out of the
|
# hook that builds it on install was running all along, and the result was then packed out of the
|
||||||
# package, because with no explicit file list npm falls back to .gitignore and that ignores the
|
# package, because with no explicit file list npm falls back to .gitignore and that ignores the
|
||||||
@@ -32,9 +40,7 @@ COPY src ./src
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ---- what the running image needs, and nothing else -------------------------------------------
|
# ---- what the running image needs, and nothing else -------------------------------------------
|
||||||
# Its own stage so the toolchain image keeps its build tools while the runtime image does not. The
|
# Its own stage so the toolchain image keeps its build tools while the runtime image does not.
|
||||||
# prune has to happen somewhere, and doing it in the toolchain stage would take the compiler out of
|
|
||||||
# the image whose whole purpose is to have one.
|
|
||||||
FROM toolchain AS lean
|
FROM toolchain AS lean
|
||||||
RUN npm prune --omit=dev
|
RUN npm prune --omit=dev
|
||||||
|
|
||||||
|
|||||||
Generated
-118
@@ -1,118 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@novox/mesh-tools",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "@novox/mesh-tools",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@novox/mesh-sdk": "file:../mesh-sdk",
|
|
||||||
"amqplib": "^0.10.9"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"mesh-tools": "dist/main.js"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/amqplib": "^0.10.8",
|
|
||||||
"@types/node": "^22.20.1",
|
|
||||||
"typescript": "^5.9.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"../mesh-sdk": {
|
|
||||||
"name": "@novox/mesh-sdk",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^22.0.0",
|
|
||||||
"typescript": "^5.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@novox/mesh-sdk": {
|
|
||||||
"resolved": "../mesh-sdk",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/amqplib": {
|
|
||||||
"version": "0.10.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.8.tgz",
|
|
||||||
"integrity": "sha512-vtDp8Pk1wsE/AuQ8/Rgtm6KUZYqcnTgNvEHwzCkX8rL7AGsC6zqAfKAAJhUZXFhM/Pp++tbnUHiam/8vVpPztA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "22.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
|
|
||||||
"integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~6.21.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/amqplib": {
|
|
||||||
"version": "0.10.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.9.tgz",
|
|
||||||
"integrity": "sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"buffer-more-ints": "~1.0.0",
|
|
||||||
"url-parse": "~1.5.10"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/buffer-more-ints": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/querystringify": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/requires-port": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "5.9.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "6.21.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/url-parse": {
|
|
||||||
"version": "1.5.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
|
||||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"querystringify": "^2.1.1",
|
|
||||||
"requires-port": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"test": "node --test --experimental-strip-types 'test/*.test.ts'"
|
"test": "node --test --experimental-strip-types 'test/*.test.ts'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@novox/mesh-sdk": "git+https://git.novox.be/novox/mesh-sdk.git#9213336",
|
"@novox/mesh-sdk": "^0.1.0",
|
||||||
"amqplib": "^0.10.9"
|
"amqplib": "^0.10.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+3
-3
@@ -38,7 +38,7 @@ export interface Credential {
|
|||||||
* Connect to the mesh broker and return a Broker. `close()` tears both channel and connection down.
|
* Connect to the mesh broker and return a Broker. `close()` tears both channel and connection down.
|
||||||
*
|
*
|
||||||
* A scoped module (novox/hq ADR 0043) passes `assumeExchanges: true`: its account may not declare
|
* A scoped module (novox/hq ADR 0043) passes `assumeExchanges: true`: its account may not declare
|
||||||
* an exchange, and the substrate already owns them, so it declares only its own queue. A credential
|
* an exchange, and the foundation already owns them, so it declares only its own queue. A credential
|
||||||
* carrying a fingerprint is dialled over amqps, pinned to exactly that certificate.
|
* carrying a fingerprint is dialled over amqps, pinned to exactly that certificate.
|
||||||
*/
|
*/
|
||||||
export async function connectAmqp(
|
export async function connectAmqp(
|
||||||
@@ -55,9 +55,9 @@ export async function connectAmqp(
|
|||||||
// at-least-once starts at the emitter, not only the consumer (ADR 0042).
|
// at-least-once starts at the emitter, not only the consumer (ADR 0042).
|
||||||
const ch = await conn.createConfirmChannel();
|
const ch = await conn.createConfirmChannel();
|
||||||
|
|
||||||
// The substrate owns the exchanges (ADR 0043). A bootstrap/admin connection declares them; a
|
// The foundation owns the exchanges (ADR 0043). A bootstrap/admin connection declares them; a
|
||||||
// scoped module assumes they exist and never tries — its account could not, and the dead-letter
|
// scoped module assumes they exist and never tries — its account could not, and the dead-letter
|
||||||
// queue behind the exchange is the substrate's to keep, not a module's.
|
// queue behind the exchange is the foundation's to keep, not a module's.
|
||||||
if (!opts.assumeExchanges) {
|
if (!opts.assumeExchanges) {
|
||||||
await ch.assertExchange(RPC_EXCHANGE, "topic", { durable: true });
|
await ch.assertExchange(RPC_EXCHANGE, "topic", { durable: true });
|
||||||
await ch.assertExchange(EVENTS_EXCHANGE, "topic", { durable: true });
|
await ch.assertExchange(EVENTS_EXCHANGE, "topic", { durable: true });
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ import { emit } from "@novox/mesh-sdk/events";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect the way this process is meant to: with its sealed credential if the mesh gave it one, and
|
* Connect the way this process is meant to: with its sealed credential if the mesh gave it one, and
|
||||||
* over the plain bootstrap URL otherwise. A scoped module assumes the substrate's exchanges exist —
|
* over the plain bootstrap URL otherwise. A scoped module assumes the foundation's exchanges exist —
|
||||||
* its account may not declare them (ADR 0043).
|
* its account may not declare them (ADR 0043).
|
||||||
*/
|
*/
|
||||||
async function connectBroker(): Promise<Broker> {
|
async function connectBroker(): Promise<Broker> {
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ test("a module's tool serves and is invoked over a real AMQP broker", { skip: !u
|
|||||||
const serverBroker = await connectAmqp(url!);
|
const serverBroker = await connectAmqp(url!);
|
||||||
const stop = await runTools({ broker: serverBroker, moduleEntrypoints: [] });
|
const stop = await runTools({ broker: serverBroker, moduleEntrypoints: [] });
|
||||||
|
|
||||||
// A separate connection — a caller, like mesh-control's command API — invokes over the broker,
|
// A separate connection — a caller, like mesh-controller's command API — invokes over the broker,
|
||||||
// by module and tool (novox/hq ADR 0047: served on serve.demo.greet, invoked as demo.greet).
|
// by module and tool (novox/hq ADR 0047: served on serve.demo.greet, invoked as demo.greet).
|
||||||
const caller = await connectAmqp(url!);
|
const caller = await connectAmqp(url!);
|
||||||
const result = (await invokeTool(caller, "demo", "greet", { who: "mesh" })) as { hello: string };
|
const result = (await invokeTool(caller, "demo", "greet", { who: "mesh" })) as { hello: string };
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { emit, on, type Event } from "@novox/mesh-sdk/events";
|
|||||||
// headers on the wire, a body that is only the payload, persistent messages, a durable per-consumer
|
// headers on the wire, a body that is only the payload, persistent messages, a durable per-consumer
|
||||||
// queue, dead-letter, poison handling? This tests the adapter in isolation, against a disposable
|
// queue, dead-letter, poison handling? This tests the adapter in isolation, against a disposable
|
||||||
// broker that stands in for the one the mesh hosts (ADR 0001). It is NOT an event test of the mesh:
|
// broker that stands in for the one the mesh hosts (ADR 0001). It is NOT an event test of the mesh:
|
||||||
// that lives in a full lab scenario where the mesh raises the broker as substrate. Requires
|
// that lives in a full lab scenario where the mesh raises the broker as foundation. Requires
|
||||||
// $MESH_BROKER_URL (a throwaway broker); skipped, never failed, when it is not set.
|
// $MESH_BROKER_URL (a throwaway broker); skipped, never failed, when it is not set.
|
||||||
const url = process.env.MESH_BROKER_URL;
|
const url = process.env.MESH_BROKER_URL;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user