@ingenioz-it/caits 0.6.0 → 0.8.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.
Files changed (57) hide show
  1. package/bin/caits.js +20 -7
  2. package/bin/init.js +16 -1
  3. package/package.json +8 -5
  4. package/quality/architecture/adapters/scss.mjs +2 -2
  5. package/quality/architecture/facts.mjs +39 -11
  6. package/quality/architecture/project.mjs +8 -6
  7. package/quality/dev.mjs +6 -3
  8. package/quality/mutation-summary.mjs +30 -20
  9. package/quality/mutation.mjs +170 -14
  10. package/src/adapter.js +94 -0
  11. package/src/di/Container.ts +27 -0
  12. package/src/di/RequestContext.ts +8 -2
  13. package/src/di/backend.ts +12 -0
  14. package/src/di/createBackend.ts +197 -25
  15. package/src/di/declareDependencies.js +25 -5
  16. package/src/di/discover.ts +19 -9
  17. package/src/di/discoverFrontends.ts +4 -8
  18. package/src/di/discovered.ts +2 -2
  19. package/src/di/discoveredFrontends.ts +2 -2
  20. package/src/messaging/BackendEventBus.ts +2 -3
  21. package/src/messaging/ContextEvents.ts +11 -0
  22. package/src/messaging/ContextMap.ts +3 -4
  23. package/src/messaging/Controller.ts +36 -75
  24. package/src/messaging/EventSubscriber.ts +1 -1
  25. package/src/messaging/InProcessMessageBus.ts +8 -9
  26. package/src/messaging/MessageBus.ts +4 -2
  27. package/src/messaging/Request.ts +116 -0
  28. package/src/messaging/RequestEvents.ts +8 -0
  29. package/src/messaging/StreamController.ts +261 -0
  30. package/src/messaging/TimerController.ts +25 -0
  31. package/src/messaging/sse.ts +23 -0
  32. package/src/server/entry.ts +37 -0
  33. package/src/server/kernel.ts +19 -0
  34. package/src/server/packages.d.ts +12 -0
  35. package/src/server/serve.js +121 -0
  36. package/src/state/createStore.ts +1 -2
  37. package/src/testing/backend/application.ts +1 -1
  38. package/src/testing/backend/applicationScenario.ts +6 -7
  39. package/src/testing/backend/applications.ts +4 -1
  40. package/src/testing/backend/context-map.ts +1 -1
  41. package/src/testing/backend/integration.ts +1 -1
  42. package/src/testing/backend/presentationApplication.ts +23 -3
  43. package/src/testing/backend/presentationScenario.ts +84 -7
  44. package/src/testing/frontend/applicationScenario.ts +2 -3
  45. package/src/testing/frontend/applications.ts +3 -3
  46. package/src/testing/frontend/frontends.ts +3 -4
  47. package/src/testing/shared/FakeMessageBus.ts +39 -5
  48. package/src/virtual-modules.d.ts +0 -2
  49. package/src/vite.js +2 -2
  50. package/src/vite.types.d.ts +7 -0
  51. package/template/caits/architecture.mjs +162 -113
  52. package/template/caits/imports.mjs +2 -2
  53. package/template/caits/stryker.mjs +11 -6
  54. package/template/caits/vite.ts +15 -5
  55. package/template/project/AGENTS +1 -1
  56. package/quality/mutation-suite.mjs +0 -82
  57. package/src/messaging/Input.ts +0 -4
@@ -13,7 +13,7 @@ A CAITS project (Clean Architecture In TypeScript): a Marko app with a backend a
13
13
 
14
14
  - The business lives in bounded contexts: `src/backend/<context>/` and `src/frontend/<context>/`, each with `domain/`, `infra/`, its use cases in `command/` and `query/`, and a `di.ts`. A frontend context has `event/` and `presentation/` too. To add one, copy the shape of an existing one.
15
15
  - Domain: entities, value objects, errors, domain events, and ports (`domain/port/<port>/`), each with its contract (`<Port>.contract.ts`).
16
- - Use cases: one folder per use case, `command/<use-case>/` or `query/<use-case>/`, which holds all of it: its DTOs, its handler and its test, `<use-case>.application.test.ts`, and in the backend its controllers, `<use-case>.<name>.controller.ts` (`<use-case>.http.controller.ts` for HTTP), each with its test, `<use-case>.<name>.presentation.test.ts`. An endpoint lives in one folder, and the name of a test says the layer that it tests. A use case imports nothing from another use case: what they share lives in the domain.
16
+ - Use cases: one folder per use case, `command/<use-case>/` or `query/<use-case>/`, which holds all of it: its DTOs (plain classes), its handler and its test, `<use-case>.application.test.ts`, and in the backend its controllers, `<use-case>.<name>.controller.ts` (`<use-case>.http.controller.ts` for HTTP), each with its test, `<use-case>.<name>.presentation.test.ts`. An endpoint lives in one folder, and the name of a test says the layer that it tests. A use case imports nothing from another use case: what they share lives in the domain. The `execute` of a handler takes the input DTO, typed with its class: CAITS finds the use case by this type.
17
17
  - Infra: the adapters of the ports, in `infra/<port>/`. The test of each adapter runs the contract of its port.
18
18
  - Frontend: its events, one file each in `event/`, and its presentation, which serves several use cases: the views (`presentation/view/<view>/`), which the routes place; the components, `presentation/component/<component>/`, the parts that the views place; and the store (`presentation/store/`), if the context has use cases. A view and a component take the frontend of their context only, publish its events and read its store. A view imports no other view, and a component no view.
19
19
  - The contexts talk through the context shared, `src/backend/shared/` and `src/frontend/shared/`, and their context map, `context-map.ts` beside their di.ts. A handler (in the frontend, also a view or a component) publishes an event of its context; the context map, `share(Event, event => [...])`, turns it into events of shared on the shared bus (the event of the context holds what they need). The context map of each context that hears it, `when(Event, event => [...])`, turns it into input DTOs of the context (backend) or into events of the context (frontend). Each line goes one way, reads only its event, and the lines make no cycle. A context names only itself and shared, never another context: only its context map imports the events of shared.
@@ -1,82 +0,0 @@
1
- // `caits mutation-suite`: the command that Stryker runs for each mutant. It runs the unit tests from a private copy of the sandbox.
2
- // Stryker runs many suites at once in a single sandbox, and every Vitest start rewrites the files that the Marko plugin generates
3
- // (dist/, .marko-run/): suites that run at the same time would fail at random, and a failed run counts as a killed mutant.
4
- // The copy shares the packages of the sandbox, not the caches that the tools keep beside them (node_modules/.vite/, .vitest-cache/…): the
5
- // cache of Vite depends on the folder of the project, so each suite would write it again while the others read it.
6
- import { spawn } from "node:child_process";
7
- import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, symlinkSync } from "node:fs";
8
- import { constants, tmpdir } from "node:os";
9
- import { join } from "node:path";
10
- import { painterOf } from "./terminal.mjs";
11
-
12
- const ENTRIES = ["src", "test", "quality", "dist", ".marko-run", "package.json", "tsconfig.json"];
13
-
14
- /** The report of Vitest, in the copy: Vitest writes it once the tests ran, also when one fails. */
15
- const REPORT = "vitest-report.json";
16
-
17
- /**
18
- * What the suite prints when the unit tests did not run. Stryker counts the mutant as killed, as for any exit code but 0:
19
- * `caits mutation-summary` finds these words in the report of Stryker, and does not.
20
- */
21
- export const NOT_RUN = "The unit tests did not run";
22
-
23
- /** The copies, in the temporary folder: `caits-mutation-<pid of their suite>-<random>`. */
24
- const COPY = /^caits-mutation-(\d+)-/;
25
-
26
- /** @param {string[]} args the options of Vitest, which Stryker gives */
27
- export async function run(args) {
28
- removeOrphans();
29
- const sandbox = process.cwd();
30
- const copy = mkdtempSync(join(tmpdir(), `caits-mutation-${process.pid}-`));
31
- const remove = () => rmSync(copy, { recursive: true, force: true });
32
- for (const entry of ENTRIES) {
33
- if (existsSync(join(sandbox, entry))) cpSync(join(sandbox, entry), join(copy, entry), { recursive: true });
34
- }
35
- // The packages and their commands (.bin/). The other entries that start with a dot are the caches and the files of the tools.
36
- mkdirSync(join(copy, "node_modules"));
37
- for (const entry of readdirSync(join(sandbox, "node_modules"))) {
38
- if (!entry.startsWith(".") || entry === ".bin") symlinkSync(join(sandbox, "node_modules", entry), join(copy, "node_modules", entry));
39
- }
40
-
41
- // The suite builds in the dist/ of its copy: the build folder that `caits check` gives (CAITS_OUT_DIR) is in node_modules/, which the
42
- // suites share.
43
- const { CAITS_OUT_DIR: _, ...env } = process.env;
44
- const vitest = ["vitest", "run", "--config", "quality/vite.config.ts", "--reporter=dot", "--reporter=json", `--outputFile.json=${REPORT}`, ...args];
45
- const suite = spawn("npx", vitest, { cwd: copy, env, stdio: "inherit" });
46
- // Ctrl+C and `caits check` stop the suite: the copy goes away with it. The exit code tells the signal: 130 for SIGINT, 143 for SIGTERM.
47
- for (const signal of /** @type {const} */ (["SIGINT", "SIGTERM"])) process.on(signal, () => { suite.kill(signal); remove(); process.exit(128 + constants.signals[signal]); });
48
- /** @type {number} */
49
- const status = await new Promise(resolve => suite.on("close", code => resolve(code ?? 1)));
50
- const ran = existsSync(join(copy, REPORT));
51
- remove();
52
- // Vitest stopped before the tests, on an error of its own or of the system: no test failed, and the mutant is not tested.
53
- if (status !== 0 && !ran) console.error(painterOf(process.stderr)("red", `${NOT_RUN}: Vitest stopped before them (exit ${status}). The mutant is not tested.`));
54
- return status;
55
- }
56
-
57
- /**
58
- * Stryker stops a suite that takes too long with SIGKILL, which no code can follow: its copy stays. Each suite removes the copies whose
59
- * suite no longer runs.
60
- */
61
- function removeOrphans() {
62
- for (const entry of readdirSync(tmpdir())) {
63
- const pid = COPY.exec(entry)?.[1];
64
- if (pid === undefined || runs(Number(pid))) continue;
65
- try {
66
- rmSync(join(tmpdir(), entry), { recursive: true, force: true });
67
- } catch {
68
- // The copy of another user.
69
- }
70
- }
71
- }
72
-
73
- /** @param {number} pid */
74
- function runs(pid) {
75
- try {
76
- process.kill(pid, 0);
77
- return true;
78
- } catch (error) {
79
- // EPERM: the process runs, but belongs to another user.
80
- return /** @type {NodeJS.ErrnoException} */ (error).code === "EPERM";
81
- }
82
- }
@@ -1,4 +0,0 @@
1
- /** An input DTO of a context, a command or a query. `Output` is the DTO its handler answers with. */
2
- export abstract class Input<Output> {
3
- declare protected readonly output?: Output;
4
- }