lakutata 3.0.0 → 3.1.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 (117) hide show
  1. package/README.md +50 -30
  2. package/README.zh-CN.md +130 -0
  3. package/dist/cjs/exports/QueueComponent.d.ts +1 -0
  4. package/dist/cjs/exports/QueueComponent.js +20 -0
  5. package/dist/esm/exports/QueueComponent.js +1 -0
  6. package/dist/types/exports/QueueComponent.d.ts +1 -0
  7. package/doc/en/Application.md +107 -0
  8. package/doc/en/CLI.md +274 -0
  9. package/doc/en/Cache.md +379 -0
  10. package/doc/en/Command.md +110 -0
  11. package/doc/en/ContainerdIntegration.md +17 -16
  12. package/doc/en/Controllers.md +256 -0
  13. package/doc/en/DBus.md +291 -0
  14. package/doc/en/DTOValidation.md +72 -43
  15. package/doc/en/Database.md +188 -0
  16. package/doc/en/DatabaseDrivers.md +287 -0
  17. package/doc/en/Decorator.md +52 -40
  18. package/doc/en/DockerIntegration.md +7 -6
  19. package/doc/en/Entities.md +240 -0
  20. package/doc/en/Entrypoint.md +216 -0
  21. package/doc/en/Exceptions.md +174 -0
  22. package/doc/en/GRPC.md +251 -0
  23. package/doc/en/GettingStarted.md +165 -0
  24. package/doc/en/HTTP.md +507 -0
  25. package/doc/en/HelperFunctions.md +11 -11
  26. package/doc/en/I18n.md +119 -115
  27. package/doc/en/Injection.md +144 -0
  28. package/doc/en/Installers.md +280 -0
  29. package/doc/en/Logger.md +206 -0
  30. package/doc/en/MCP.md +293 -0
  31. package/doc/en/Middlewares.md +213 -0
  32. package/doc/en/Migrations.md +202 -0
  33. package/doc/en/MongoEmbedded.md +149 -0
  34. package/doc/en/Monitor.md +164 -0
  35. package/doc/en/Mutex.md +27 -25
  36. package/doc/en/NATS.md +246 -0
  37. package/doc/en/NativeAddons.md +212 -0
  38. package/doc/en/Objects.md +143 -0
  39. package/doc/en/Overview.md +62 -85
  40. package/doc/en/Packaging.md +193 -0
  41. package/doc/en/ProjectStructure.md +124 -0
  42. package/doc/en/Queue.md +269 -0
  43. package/doc/en/Schedules.md +217 -0
  44. package/doc/en/Security.md +10 -9
  45. package/doc/en/ServiceCalls.md +504 -0
  46. package/doc/en/Time.md +19 -20
  47. package/doc/en/UpgradingFrom1.md +435 -0
  48. package/doc/en/UpgradingFrom2.md +315 -0
  49. package/doc/en/Workspaces.md +189 -0
  50. package/doc/en/examples/BackgroundJobs.md +677 -0
  51. package/doc/en/examples/CommandLineTool.md +664 -0
  52. package/doc/en/examples/DesktopApp.md +809 -0
  53. package/doc/en/examples/DeviceService.md +607 -0
  54. package/doc/en/examples/MCPServer.md +653 -0
  55. package/doc/en/examples/README.md +16 -0
  56. package/doc/en/examples/RestAPI.md +490 -0
  57. package/doc/en/examples/ServiceCalls.md +829 -0
  58. package/doc/en/examples/WebApp.md +568 -0
  59. package/doc/zh/Application.md +75 -0
  60. package/doc/zh/CLI.md +250 -0
  61. package/doc/zh/Cache.md +328 -0
  62. package/doc/zh/Command.md +78 -0
  63. package/doc/zh/ContainerdIntegration.md +9 -9
  64. package/doc/zh/Controllers.md +228 -0
  65. package/doc/zh/DBus.md +257 -0
  66. package/doc/zh/DTOValidation.md +49 -19
  67. package/doc/zh/Database.md +175 -0
  68. package/doc/zh/DatabaseDrivers.md +261 -0
  69. package/doc/zh/Decorator.md +25 -18
  70. package/doc/zh/DockerIntegration.md +6 -6
  71. package/doc/zh/Entities.md +224 -0
  72. package/doc/zh/Entrypoint.md +193 -0
  73. package/doc/zh/Exceptions.md +158 -0
  74. package/doc/zh/GRPC.md +226 -0
  75. package/doc/zh/GettingStarted.md +148 -0
  76. package/doc/zh/HTTP.md +445 -0
  77. package/doc/zh/HelperFunctions.md +2 -2
  78. package/doc/zh/I18n.md +26 -26
  79. package/doc/zh/Injection.md +129 -0
  80. package/doc/zh/Installers.md +232 -0
  81. package/doc/zh/Logger.md +188 -0
  82. package/doc/zh/MCP.md +265 -0
  83. package/doc/zh/Middlewares.md +198 -0
  84. package/doc/zh/Migrations.md +178 -0
  85. package/doc/zh/MongoEmbedded.md +118 -0
  86. package/doc/zh/Monitor.md +145 -0
  87. package/doc/zh/Mutex.md +10 -10
  88. package/doc/zh/NATS.md +207 -0
  89. package/doc/zh/NativeAddons.md +180 -0
  90. package/doc/zh/Objects.md +119 -0
  91. package/doc/zh/Overview.md +46 -71
  92. package/doc/zh/Packaging.md +154 -0
  93. package/doc/zh/ProjectStructure.md +102 -0
  94. package/doc/zh/Queue.md +214 -0
  95. package/doc/zh/Schedules.md +193 -0
  96. package/doc/zh/Security.md +6 -6
  97. package/doc/zh/ServiceCalls.md +459 -0
  98. package/doc/zh/Time.md +2 -2
  99. package/doc/zh/UpgradingFrom1.md +347 -0
  100. package/doc/zh/UpgradingFrom2.md +230 -0
  101. package/doc/zh/Workspaces.md +150 -0
  102. package/doc/zh/examples/BackgroundJobs.md +618 -0
  103. package/doc/zh/examples/CommandLineTool.md +618 -0
  104. package/doc/zh/examples/DesktopApp.md +748 -0
  105. package/doc/zh/examples/DeviceService.md +546 -0
  106. package/doc/zh/examples/MCPServer.md +604 -0
  107. package/doc/zh/examples/README.md +14 -0
  108. package/doc/zh/examples/RestAPI.md +458 -0
  109. package/doc/zh/examples/ServiceCalls.md +765 -0
  110. package/doc/zh/examples/WebApp.md +520 -0
  111. package/package.json +46 -17
  112. package/doc/en/ActionPattern.md +0 -408
  113. package/doc/en/Core.md +0 -312
  114. package/doc/en/ORMIntegration.md +0 -341
  115. package/doc/zh/ActionPattern.md +0 -334
  116. package/doc/zh/Core.md +0 -256
  117. package/doc/zh/ORMIntegration.md +0 -319
@@ -0,0 +1,110 @@
1
+ # Lakutata Command Line Tool
2
+
3
+ The **`lakutata` command** (package `@lakutata/cli`) creates projects from templates, runs scripts across the packages
4
+ of a workspace and gives them versions, adds packages and native addons, runs the migrations of an application, checks
5
+ its translations, and packs it into bundles, executables and installers. The README of `@lakutata/cli` lists every
6
+ option.
7
+
8
+ ## 1. Installing and running it
9
+
10
+ ```bash
11
+ npx @lakutata/cli create --name my-app # without installing it
12
+ npm install --global @lakutata/cli # or once for the machine, then: lakutata create --name my-app
13
+ ```
14
+
15
+ The templates add `@lakutata/cli` to the `devDependencies` of the project, so its scripts call `lakutata` directly and
16
+ `npx lakutata` works inside it. The tool needs Node.js `^22.13.0 || >=24.11.0`: 22.13 or a later 22.x, or 24.11 and
17
+ later (not 23).
18
+
19
+ - `lakutata --help` lists the commands, `lakutata help <command>` (or `lakutata <command> --help`) describes one, and
20
+ `lakutata --version` prints the version of the tool. Without a command, the tool prints its help and exits with code
21
+ 1.
22
+ - Options are written in kebab case (`--package-manager`). A boolean option that is on by default is turned off with
23
+ `--no-` (`--no-git`, `--no-bail`).
24
+ - Results go to standard output, warnings (`! …`) to standard error. A failure prints `error: <message>` on standard
25
+ error and exits with code 1. Colors are used on a terminal only, and `NO_COLOR` turns them off.
26
+ - **Language**: the help, the prompts and the validation errors are in English, Simplified Chinese or Traditional
27
+ Chinese, chosen from `LAKUTATA_LANG`, then `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG`, then the language of the
28
+ system (`LAKUTATA_LANG=zh-CN lakutata --help`). The names of commands and options, and the other messages of the
29
+ tool, stay in English.
30
+
31
+ ## 2. Projects
32
+
33
+ ### `lakutata create`
34
+
35
+ ```bash
36
+ lakutata create --name my-app # the default template, in ./my-app
37
+ lakutata create --name shop --template service --with i18n,installer --yes
38
+ lakutata create --name tool --template cli --package-manager none --no-git
39
+ ```
40
+
41
+ | Option | Default | Meaning |
42
+ |---|---|---|
43
+ | `--name` | asked in a terminal | The name of the application, and of its directory |
44
+ | `--id` | the name in lower case | The ID of the application and the name of its package |
45
+ | `--template` | `default` | An official template (`lakutata templates`), `npm:<package>[@<version>]`, `github:<owner>/<repo>[#<ref>]`, or a directory |
46
+ | `--path` | `.` | Where the project directory is created (`<path>/<name>`) |
47
+ | `--init` | | Create the project in `--path` itself, a directory that already exists |
48
+ | `--description`, `--author`, `--license` | license `UNLICENSED` | The information of the package |
49
+ | `--with` | | The optional features of the template, separated by commas (`i18n`, `installer`) |
50
+ | `--package-manager` | `npm`, or pnpm or yarn when one of them started the tool | `npm`, `pnpm`, `yarn`, or `none` to skip the installation |
51
+ | `--no-git` | | Do not create a git repository |
52
+ | `--yes` | | Do not ask for the features or for a confirmation |
53
+
54
+ In a terminal, the tool asks for a missing name, offers the features of the template, shows what it is going to create
55
+ and asks for a confirmation. It then copies the template, fills in the name, the ID and the other information of the
56
+ package, installs the dependencies (`<manager> install`) and runs `git init` (without a commit). An official template
57
+ is the newest release for the major version of the tool, downloaded from the npm registry and kept in the cache of the
58
+ tool; without a network, a template downloaded earlier is used when there is one.
59
+
60
+ With `--init`, the files the directory already has are kept: a file of the template with the same name is not copied,
61
+ and a warning lists these files so that you can merge what you need by hand (for `package.json`, its scripts and
62
+ dependencies).
63
+
64
+ ### `lakutata templates`, `info` and `clean`
65
+
66
+ - `lakutata templates` lists the official templates and their features. The list comes from the npm registry and is
67
+ kept for a day (`--refresh` fetches it again). [ProjectStructure](ProjectStructure.md) describes what the templates
68
+ contain.
69
+ - `lakutata info` shows the versions of the tool, of the framework (with the latest ones when the registry can be
70
+ reached) and of Node.js, and the version of the framework that the project of the current directory uses.
71
+ - `lakutata clean [paths...]` removes paths of the package in the current directory, on every platform; without paths,
72
+ the `outDir` and `declarationDir` of its `tsconfig.json`. It refuses paths outside the package, and `src`,
73
+ `node_modules`, `package.json` and `.git`. The templates call it from their `build` or `rebuild` scripts.
74
+
75
+ ## 3. Workspaces and native addons
76
+
77
+ `lakutata list`, `run`, `version` and `add` work on a workspace, a repository of several packages: they list its
78
+ packages, run a script in each of them in the order of their dependencies, give them one version, and add packages. See
79
+ [Workspaces](Workspaces.md).
80
+
81
+ `lakutata add <name> --native gyp|cmake|rust` adds a native addon written in C++ or Rust, and
82
+ `lakutata native <link|include|define> <value>` links libraries to it. See [NativeAddons](NativeAddons.md).
83
+
84
+ ## 4. Migrations and translations
85
+
86
+ `lakutata migration <action>` generates, creates, runs, reverts and shows the migrations of the databases of an
87
+ application. It builds and starts the application with its `dev` script, in a mode where only the databases are
88
+ connected. See [Migrations](Migrations.md), section 2.
89
+
90
+ `lakutata i18n <action>` checks the messages of the languages of an application, declares the types of their keys, and
91
+ adds a language. See [I18n](I18n.md#command-line-tools).
92
+
93
+ ## 5. Packaging
94
+
95
+ `lakutata package bundle` and `package exe` pack a compiled application into one JavaScript file, or into an executable
96
+ that runs without Node.js: see [Packaging](Packaging.md). `lakutata package installer` makes its installers, which can
97
+ register it as a service: see [Installers](Installers.md).
98
+
99
+ ## 6. Environment variables
100
+
101
+ | Variable | Meaning |
102
+ |---|---|
103
+ | `LAKUTATA_LANG` | The language of the tool (`en`, `zh-CN`, `zh-TW`) |
104
+ | `LAKUTATA_NPM_REGISTRY` | The registry the templates come from (else `npm_config_registry`, else `https://registry.npmjs.org`) |
105
+ | `LAKUTATA_GITHUB_DOWNLOAD` | Where the `github:` templates are downloaded from (`https://github.com` by default) |
106
+ | `LAKUTATA_CACHE_DIR` | The cache of the tool: the templates, and the tools that make installers (`$XDG_CACHE_HOME/lakutata`, else `~/.cache/lakutata`) |
107
+ | `NO_COLOR` | Turns the colors off |
108
+
109
+ The scripts and the applications the tool runs get the environment the tool was started with. The variables of the
110
+ packed applications are described in [Packaging](Packaging.md), section 6.
@@ -1,21 +1,21 @@
1
- # Lakutata containerd Integration
1
+ # Lakutata Containerd Integration
2
2
 
3
3
  The **containerd component** (`lakutata/com/containerd`, package `@lakutata/containerd`) manages the images, containers,
4
4
  CNI networks and named volumes of a [containerd](https://containerd.io) namespace over the gRPC API of containerd,
5
5
  without Docker and without nerdctl. It targets the Linux machines that run containerd alone, such as embedded devices:
6
6
  the component talks to the socket of containerd (its gRPC client is written in TypeScript, no native addon), and does
7
7
  by itself what nerdctl does on top of it (pulling from the registries, unpacking the layers, generating the OCI
8
- specifications of the containers, their logs). Its API follows the one of the Docker component
8
+ specifications of the containers, their logs). Its API follows that of the Docker component
9
9
  ([DockerIntegration](DockerIntegration.md)). The README of `@lakutata/containerd` gives every option.
10
10
 
11
- The containers it creates are the ones of nerdctl: they carry its labels (`nerdctl/name`, `nerdctl/hostname`…), so
12
- `nerdctl ps` and `ctr containers ls` list them.
11
+ The containers it creates are the same as the containers nerdctl creates: they carry its labels (`nerdctl/name`,
12
+ `nerdctl/hostname`…), so `nerdctl ps` and `ctr containers ls` list them.
13
13
 
14
14
  ## 1. Requirements
15
15
 
16
16
  | Feature | Needs |
17
17
  |---|---|
18
- | Everything | Linux, containerd 1.6 or later with runc, Node.js 22 or later; the application runs as root or as a member of the group owning the socket of containerd |
18
+ | Everything | Linux, containerd 1.6 or later with runc, Node.js `^22.13.0 || >=24.11.0`; the application runs as root or as a member of the group owning the socket of containerd |
19
19
  | CNI networks (the default `bridge` included), published ports | The CNI plugins (`bridge`, `portmap`, `host-local`… in `/opt/cni/bin`, `/usr/lib/cni` or `/usr/libexec/cni`, the `cniPath` option) and iptables |
20
20
  | `exec()` with `stdin`, separate `stdout`/`stderr`, terminals | `mkfifo` on the host (without it, `exec()` puts both outputs in `stdout`) |
21
21
  | Builds | A BuildKit daemon (`buildkitd`), local or remote |
@@ -42,7 +42,7 @@ Application
42
42
  cgroupManager: 'cgroupfs',
43
43
  //The logs, hosts, resolv.conf and settings of the containers, and the named volumes
44
44
  dataRoot: '/var/lib/lakutata/containerd',
45
- //The log file of a container rotated beyond 10 MiB when the component starts it (0: never)
45
+ //The log file of a container rotated beyond 10 MiB when the component starts it (default 0: never)
46
46
  logMaxSize: 10 * 1024 * 1024,
47
47
  registryAuths: {'registry.example.com': {username: 'deploy', password: 'secret'}},
48
48
  //The registries served over HTTP
@@ -63,9 +63,10 @@ Application
63
63
  })
64
64
  ```
65
65
 
66
- The component connects when it is created: without containerd on `socketPath`, the application fails to start with a
67
- `ContainerdConnectionException`. Its namespace is created when it does not exist. Each component works in one
68
- namespace; declare one component per namespace to manage several.
66
+ The component connects when it is created: without containerd on `socketPath`, its creation fails with a
67
+ `ContainerdConnectionException`; when the component is listed in `bootstrap`, the application then fails to start. Its
68
+ namespace is created when it does not exist. Each component works in one namespace; declare one component per namespace
69
+ to manage several.
69
70
 
70
71
  ### What differs from the Docker component
71
72
 
@@ -178,7 +179,7 @@ async function containers(containerd: Containerd): Promise<void> {
178
179
  //Its snapshot and its state removed
179
180
  await container.remove({force: true})
180
181
 
181
- //By its name, its identifier or a prefix of it; by labels
182
+ //The containers with these labels; getContainer() finds one by its name, its id or a prefix of its id
182
183
  console.log(await containerd.listContainers({app: 'worker'}))
183
184
  }
184
185
  ```
@@ -193,8 +194,8 @@ async function containers(containerd: Containerd): Promise<void> {
193
194
  `seccompProfile: 'unconfined'` disables it, the path of a file gives an OCI profile; the privileged containers are
194
195
  unconfined.
195
196
  - `privileged`, `capabilities` / `capabilitiesDrop`, `readonlyRootfs`, `pidMode: 'host'`, `ipcMode: 'host'`, `sysctls`,
196
- `extraHosts`, `dns`, `shmSize`, `pidsLimit`, `cpuSet`, `cpuShares`, `memorySwap`, `OOMKillDisable` are the options of
197
- Docker of the same names.
197
+ `extraHosts`, `dns`, `shmSize`, `pidsLimit`, `cpuSet`, `cpuShares`, `memorySwap`, `OOMKillDisable` work as the options
198
+ of Docker with the same names.
198
199
  - `update()` with other settings than the resources and the restart policy only works for the containers the component
199
200
  created (it keeps their settings in `dataRoot`).
200
201
 
@@ -253,8 +254,8 @@ async function networks(containerd: Containerd): Promise<void> {
253
254
  ```
254
255
 
255
256
  The networks of a container are set up by its runc hooks, as nerdctl does: they are added when its task is created and
256
- deleted when it stops, also when containerd restarts it by its restart policy (after a reboot, without the
257
- application). Its address is given at each start and written in its `/etc/hosts`.
257
+ deleted when it stops, also when containerd restarts it under its restart policy (after a reboot, for example, while the
258
+ application is not running). Its address is given at each start and written in its `/etc/hosts`.
258
259
 
259
260
  ## 6. Builds
260
261
 
@@ -291,7 +292,7 @@ BuildKit pulls the base images with the registry credentials of the component (o
291
292
 
292
293
  ## 7. Platforms and events
293
294
 
294
- `containerd.supportedPlatforms()` gives the platforms the machine runs: its own, and the ones of the emulators
295
+ `containerd.supportedPlatforms()` gives the platforms the machine can run: its own, and the ones of the emulators
295
296
  registered in `binfmt_misc` (QEMU, Rosetta). A container of another platform runs once its emulator is registered:
296
297
  `createContainer({image: 'alpine:3.20', platform: 'linux/amd64', cmd: ['uname', '-m']})` after pulling that platform.
297
298
 
@@ -324,5 +325,5 @@ network exists, its subnet overlaps another one, or a container uses it), `Conta
324
325
 
325
326
  ## 9. Limits of this version
326
327
 
327
- - The projects of Compose are not supported.
328
+ - Compose projects are not supported.
328
329
  - The logs of the containers have no timestamps.
@@ -0,0 +1,256 @@
1
+ # Lakutata Controllers and Actions
2
+
3
+ A controller is a class whose methods answer requests. Each of these methods is an **action**: a decorator gives it an
4
+ address (an HTTP route, a command, a service pattern, a schedule, a queue), and the `entrypoint` component calls it when
5
+ a request arrives at that address. One method can have several addresses: the same code then answers over HTTP, on the
6
+ command line and over a service transport, with the same validation, middlewares and access control.
7
+
8
+ ## 1. A controller
9
+
10
+ ```typescript
11
+ import {Application, DTO} from 'lakutata'
12
+ import {buildEntrypoints, Controller, httpServiceEntrypoint} from 'lakutata/com/entrypoint'
13
+ import {CLIAction, GET, POST, ServiceAction} from 'lakutata/decorator/ctrl'
14
+ import {Expect} from 'lakutata/decorator/dto'
15
+ import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
16
+ import {commanderCLIEntrypoint} from 'lakutata/adapter/commander'
17
+
18
+ class OrderIdDTO extends DTO {
19
+ //A route parameter is text: strict(false) converts it (the command line converts its arguments itself)
20
+ @Expect(DTO.Number().integer().strict(false).required())
21
+ public id: number
22
+ }
23
+
24
+ class CreateOrderDTO extends DTO {
25
+ @Expect(DTO.String().required())
26
+ public product: string
27
+
28
+ @Expect(DTO.Number().integer().min(1).default(1))
29
+ public quantity: number
30
+ }
31
+
32
+ class OrderController extends Controller {
33
+ //GET /orders/42, the command "order 42", the service message {cmd: 'order.get', id: 42}
34
+ @GET('/orders/:id', OrderIdDTO)
35
+ @CLIAction('order <id>', OrderIdDTO, {description: 'Show an order'})
36
+ @ServiceAction({cmd: 'order.get'}, OrderIdDTO)
37
+ public async get(input: OrderIdDTO): Promise<{ id: number, status: string }> {
38
+ return {id: input.id, status: 'paid'}
39
+ }
40
+
41
+ //POST /orders with a JSON body
42
+ @POST('/orders', CreateOrderDTO)
43
+ public async create(input: CreateOrderDTO): Promise<CreateOrderDTO> {
44
+ return input
45
+ }
46
+ }
47
+
48
+ Application.run(() => ({
49
+ id: 'orders.app',
50
+ name: 'Orders',
51
+ components: {
52
+ entrypoint: buildEntrypoints({
53
+ controllers: [OrderController],
54
+ http: expressHTTPEntrypoint({port: 8080}),
55
+ service: httpServiceEntrypoint({port: 8081}),
56
+ cli: commanderCLIEntrypoint({bin: 'orders', noCommand: 'ignore'})
57
+ })
58
+ },
59
+ bootstrap: ['entrypoint']
60
+ }))
61
+ ```
62
+
63
+ ```text
64
+ $ curl localhost:8080/orders/42
65
+ {"id":42,"status":"paid"}
66
+ $ curl -X POST localhost:8080/orders -H 'Content-Type: application/json' -d '{"product":"tea","extra":1}'
67
+ {"product":"tea","quantity":1}
68
+ $ curl -X POST localhost:8081 -H 'Content-Type: application/json' -d '{"cmd":"order.get","id":42}'
69
+ {"success":true,"error":null,"payload":{"id":42,"status":"paid"}}
70
+ $ node dist/index.js order 42
71
+ {
72
+ "id": 42,
73
+ "status": "paid"
74
+ }
75
+ ```
76
+
77
+ - A controller extends `Controller`, from `lakutata/com/entrypoint`.
78
+ - The controllers are listed in the `controllers` option of `buildEntrypoints`, next to the adapters of the transports
79
+ (see [Entrypoint](Entrypoint.md)). The entrypoint must be listed in `bootstrap`, or nothing starts it.
80
+ - The decorator of a transport has no effect without its adapter: here, without `cli`, the `@CLIAction` would be
81
+ ignored.
82
+
83
+ ## 2. How a request is handled
84
+
85
+ 1. The adapter receives the request and looks for the action at its address. When there is none, the transport answers
86
+ that it is not found (404 over HTTP).
87
+ 2. A new scope is created for the request.
88
+ 3. The middlewares run (see [Middlewares](Middlewares.md)).
89
+ 4. A new instance of the controller is created in the scope.
90
+ 5. The DTO of the action validates the input (section 4).
91
+ 6. The access control checks the request (see [Middlewares](Middlewares.md)).
92
+ 7. The method is called with the input, and what it returns is the response (section 5).
93
+ 8. The scope is destroyed, with the objects created in it.
94
+
95
+ So a controller lives as long as one request. The providers it injects that are `@Scoped` (the default for a provider)
96
+ are created once for the request and shared by everything that takes part in it; the components are shared by the whole
97
+ application.
98
+
99
+ Inside the method, two properties describe the request:
100
+
101
+ - `this.context` is the context of the request. Its `type` names the transport, and its class gives the details of that
102
+ transport:
103
+
104
+ | Class | `type` | Specific properties |
105
+ |---|---|---|
106
+ | `HTTPContext` | `'HTTP'` | `request`, `response`, `route` (the route as declared), `method` |
107
+ | `CLIContext` | `'CLI'` | `command` (the command as declared) |
108
+ | `ServiceContext` | `'SERVICE'` | `clientId`, `input` (the whole message) |
109
+ | `ScheduleContext` | `'SCHEDULE'` | `schedule`, `scheduledAt`, `run` |
110
+ | `QueueContext` (`lakutata/com/queue`) | `'QUEUE'` | See [Queue](Queue.md) |
111
+
112
+ Every context also has `data`, the input as it arrived (before validation), and `locale`, the language of the
113
+ request when the I18n middleware is used.
114
+ - `this.signal` is an `AbortSignal` (its type allows `undefined`: write `this.signal?.`). It is aborted when the
115
+ request is: the client went away, the deadline of a gRPC call passed, or the application is stopping and its
116
+ `shutdownTimeout` ran out. Once it is aborted, the result of the action is dropped; a long action should check it and
117
+ stop early.
118
+
119
+ ## 3. The action decorators
120
+
121
+ | Decorator | Address | Guide |
122
+ |---|---|---|
123
+ | `@GET(route)`, `@POST`, `@PUT`, `@PATCH`, `@DELETE`, `@HEAD`, `@OPTIONS`, `@HTTPAction(route, methods)` | An HTTP route | [HTTP](HTTP.md) |
124
+ | `@CLIAction(command)` | A command | [CLI](CLI.md) |
125
+ | `@ServiceAction(pattern)` | A service pattern | [ServiceCalls](ServiceCalls.md), [GRPC](GRPC.md), [NATS](NATS.md) |
126
+ | `@Schedule(schedule)` | An interval or a cron expression | [Schedules](Schedules.md) |
127
+ | `@QueueAction(queue)` (`lakutata/com/queue`) | A message queue | [Queue](Queue.md) |
128
+ | `@MCPTool(name)` (`@lakutata/mcp`), `@DBusMethod(...)` (`@lakutata/dbus`) | An MCP tool, a D-Bus method | [MCP](MCP.md), [DBus](DBus.md) |
129
+
130
+ The decorators of the first five rows come from `lakutata/decorator/ctrl`, unless the table names another subpath.
131
+ They take the address first, then the DTO class of the input, then the action options; the DTO and the options can be
132
+ left out. `@Schedule` is the exception: it takes no DTO, as a scheduled job has no input.
133
+
134
+ The action options:
135
+
136
+ | Option | Default | Meaning |
137
+ |---|---|---|
138
+ | `name` | `'ControllerClass:method'` | The name of the action, unique for each transport |
139
+ | `description` | | A description: the help of a command, the listing of the actions |
140
+ | `middlewares` | | The middlewares of this action (see [Middlewares](Middlewares.md)) |
141
+ | `acl` | `false` | Check the request with the access rules of the entrypoint |
142
+ | `rule` | | An access rule of this action: `(context, input) => Promise<boolean>` |
143
+ | `groups` | | The groups of the action, used by the access rules and the listings (see [Entrypoint](Entrypoint.md)) |
144
+
145
+ ## 4. The input
146
+
147
+ Each transport builds the input from its request:
148
+
149
+ | Transport | Input |
150
+ |---|---|
151
+ | HTTP | The query and the body, with the route parameters over them |
152
+ | Command line | The arguments and the options |
153
+ | Service | The message, without the keys of the pattern |
154
+ | Schedule | Nothing |
155
+ | Queue | The message |
156
+
157
+ The DTO of the action then validates it:
158
+
159
+ - the values of the query and of the route parameters are text: a schema that expects a number or a boolean needs
160
+ `.strict(false)` to convert `'42'` or `'true'`. The command line gives its arguments and options the type of their
161
+ property itself ([CLI](CLI.md));
162
+ - the defaults are applied, and the keys the DTO does not declare are removed (`extra` above);
163
+ - an invalid input stops the request with an `InvalidValueException`, answered with the status 400 over HTTP. The
164
+ message names the first invalid value: `"product" is required`. With the I18n middleware, it is in the language of
165
+ the request.
166
+
167
+ The method receives the validated input as a plain object, a copy: not an instance of the DTO class, which only gives it
168
+ its type. An action without a DTO receives the input as it arrived, with all its keys.
169
+
170
+ ## 5. The result
171
+
172
+ What the method returns is sent back by the transport:
173
+
174
+ | Transport | A value | `undefined` | An error thrown |
175
+ |---|---|---|---|
176
+ | HTTP | JSON | Status 204 | Its `statusCode` (500 by default) and `{errno, errMsg}` |
177
+ | Command line | On stdout: text as it is, other values as JSON | Nothing | `error: <message>` on stderr, exit code 1 |
178
+ | Service | `{success: true, error: null, payload}` | `payload` left out | `{success: false, error, payload: null}`, the error with all its properties |
179
+ | Schedule | Ignored | | Given to `onError`, or logged |
180
+
181
+ `Result`, from `lakutata/com/entrypoint`, controls the response more closely:
182
+
183
+ - `Result.status(status, data, headers)`: an HTTP status and headers with the data;
184
+ - `Result.file(path)` (or a `Buffer`, or a stream, with `{filename, contentType, etag, headers}`): a file, as a download
185
+ when `filename` is given;
186
+ - `Result.events(producer)`: Server-Sent Events over HTTP, the data of each event on the command line. The producer
187
+ `(send, signal) => …` runs after the scope of the request is destroyed, so it should use components rather than
188
+ request-scoped providers.
189
+
190
+ The service transports cannot send a file or events: such a result fails the request.
191
+
192
+ ## 6. Prefixes
193
+
194
+ `@Prefix` on a controller adds a prefix to the addresses of all its actions:
195
+
196
+ ```typescript
197
+ import {DTO} from 'lakutata'
198
+ import {Controller} from 'lakutata/com/entrypoint'
199
+ import {CLIAction, GET, Prefix, ServiceAction} from 'lakutata/decorator/ctrl'
200
+ import {Expect} from 'lakutata/decorator/dto'
201
+
202
+ class UserIdDTO extends DTO {
203
+ @Expect(DTO.String().required())
204
+ public id: string
205
+ }
206
+
207
+ @Prefix({route: '/users', command: 'user', pattern: {resource: 'user'}})
208
+ class UserController extends Controller {
209
+ //GET /users/:id, the command "user:get <id>" and the message {resource: 'user', act: 'get', id}
210
+ @GET('/:id', UserIdDTO)
211
+ @CLIAction('get <id>', UserIdDTO)
212
+ @ServiceAction({act: 'get'}, UserIdDTO)
213
+ public async get(input: UserIdDTO): Promise<{ id: string }> {
214
+ return {id: input.id}
215
+ }
216
+ }
217
+ ```
218
+
219
+ - `route` is put before the HTTP routes, `command` before the commands (joined by a colon), and `pattern` is merged
220
+ into the service patterns. A key set to different values in the prefix and in an action is an error
221
+ (`InvalidPrefixException`).
222
+ - The scheduled jobs have no prefix.
223
+ - A controller takes the prefix of the nearest class that declares one: its own, or else the one of its parent.
224
+
225
+ ## 7. Inheritance
226
+
227
+ A controller inherits the actions of its parent classes, so common actions can be written once in a base class. A
228
+ registered subclass serves them under its own prefix:
229
+
230
+ ```typescript
231
+ import {Controller} from 'lakutata/com/entrypoint'
232
+ import {GET, Prefix} from 'lakutata/decorator/ctrl'
233
+
234
+ abstract class HealthController extends Controller {
235
+ @GET('/health')
236
+ public async health(): Promise<string> {
237
+ return 'ok'
238
+ }
239
+ }
240
+
241
+ //GET /v1/health
242
+ @Prefix({route: '/v1'})
243
+ class V1Controller extends HealthController {
244
+ }
245
+
246
+ //GET /v2/health
247
+ @Prefix({route: '/v2'})
248
+ class V2Controller extends HealthController {
249
+ }
250
+ ```
251
+
252
+ - Register the subclasses only (`controllers: [V1Controller, V2Controller]`). A parent and a subclass registered
253
+ together, with the same address for an action, would conflict: the one registered last wins.
254
+ - A subclass can override an inherited method: the action keeps its address and runs the new code. A decorator on the
255
+ overriding method adds an address; the inherited one stays.
256
+ - The base class can be abstract.