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,280 @@
1
+ # Lakutata Installers
2
+
3
+ `lakutata package installer` makes the installers of an application: the packages of the system (deb, rpm, apk, pkg,
4
+ msi…) that install its executable, put its program in the PATH, and can register it as a service that starts with the
5
+ system. They are configured in the `package.json` of the application, and need `@lakutata/builder` in the project, as do
6
+ the other packing commands ([Packaging](Packaging.md)).
7
+
8
+ ## 1. Making installers
9
+
10
+ ```bash
11
+ npm run build
12
+ npx lakutata package installer # the installers of the configuration, in dist/installer/
13
+ npx lakutata package installer --target pkg # only some of them
14
+ ```
15
+
16
+ ```
17
+ › Node.js v24.11.1 embedded
18
+ › Resources: texts
19
+ ✔ dist/installer/greet-1.0.0-arm64.pkg (pkg, 36.0 MB)
20
+ ```
21
+
22
+ An installer holds the executable of the directory layout (`lakutata package exe --layout directory`): the program,
23
+ its native addons (`native/`) and its resources (`resources/`). The program is made with Node.js, or with Bun when
24
+ `lakutata.installer.runtime` is `"bun"`.
25
+
26
+ | Platform | Installers (default) | Their files | Made with |
27
+ |---|---|---|---|
28
+ | Linux (glibc) | `deb`, `rpm`, `tar.gz` (also `archlinux`) | `<name>_<version>_amd64.deb`, `<name>-<version>-1.x86_64.rpm`, `<name>-<version>-linux-amd64.tar.gz` | nFPM |
29
+ | Linux (musl, Alpine) | `apk`, `tar.gz` | `<name>-<version>-r0.x86_64.apk` | nFPM |
30
+ | macOS | `pkg`, `tar.gz` | `<name>-<version>-arm64.pkg`, `<name>-<version>-darwin-arm64.tar.gz` | `pkgbuild`, `productbuild` |
31
+ | Windows | `nsis`, `msi`, `zip` | `<name>-<version>-x64-setup.exe`, `<name>-<version>-x64.msi`, `<name>-<version>-win-x64.zip` | NSIS, WiX, WinSW |
32
+
33
+ - **On each platform**: the installers of a platform are made on that platform, for the architecture of the machine
34
+ (section 7 covers the other platforms and architectures); a `pkg` package installs on the Macs of that architecture
35
+ only. The executables use the C library of the system they are made on, so make the Linux packages for glibc systems
36
+ on a glibc system, and the `apk` packages on Alpine.
37
+ - **The tools** nFPM, NSIS, WiX and WinSW are downloaded once into the cache of the tool (`LAKUTATA_CACHE_DIR`).
38
+ `LAKUTATA_NFPM`, `LAKUTATA_NSIS`, `LAKUTATA_WIX` and `LAKUTATA_WINSW` name other copies, for machines without a
39
+ network, or for Linux architectures other than x64 and arm64 (nFPM has releases for these two only). macOS needs no
40
+ download.
41
+ - **The version** of the installers is the `version` of the `package.json` of the application.
42
+
43
+ ## 2. The configuration
44
+
45
+ The installers are configured by `lakutata.installer` in the `package.json` of the application. The one of the
46
+ `service` template, with its `installer` feature (`lakutata create --with installer`):
47
+
48
+ ```json
49
+ {
50
+ "bin": {"shop": "build/App.js"},
51
+ "scripts": {"package": "lakutata package installer"},
52
+ "lakutata": {
53
+ "icon": "../resources/logo.png",
54
+ "installer": {
55
+ "name": "shop",
56
+ "service": {"env": {"PORT": "8080"}},
57
+ "linux": {"targets": ["deb", "rpm", "tar.gz"]}
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ | Field | Default | Meaning |
64
+ |---|---|---|
65
+ | `name` | The name of the program (the first `bin`, else the name of the package without its scope), in lower case | The name of the package, of its directory and of its files: lower case letters, digits, `+`, `-` and `.` |
66
+ | `appId` | `name` | The identifier of the application (`com.acme.shop`): on macOS, the identifier of the package and the label of the service; on Windows, the key of its uninstaller and of its upgrades |
67
+ | `productName` | The `productName` of the `package.json`, else `name` | The name shown to users (in the installers, and in the programs of Windows) |
68
+ | `description`, `homepage`, `license` | Those of the `package.json` | The information of the packages |
69
+ | `maintainer` | The `author` of the `package.json` | Its maintainer (`Name <email>`); without one, the tool warns |
70
+ | `vendor` | None | Its vendor; on Windows, the publisher is the vendor, else the maintainer |
71
+ | `runtime` | `node` | `node` or `bun` for the executable; `electron` for an Electron application (section 8) |
72
+ | `addToPath` | `true` | Put the program in the PATH: `/usr/bin/<program>` on Linux, `/usr/local/bin/<program>` on macOS, the PATH of the machine on Windows |
73
+ | `service` | None | `true`, or the options of the service (section 4) |
74
+ | `linux.targets` | `deb`, `rpm`, `tar.gz`; on Alpine `apk`, `tar.gz` | The Linux installers (`deb`, `rpm`, `archlinux`, `tar.gz`; `apk`, `tar.gz` on Alpine) |
75
+ | `linux.depends`, `linux.recommends` | None | The system packages it needs and recommends (the C++ runtime library is always added) |
76
+ | `linux.section`, `linux.priority` | None | Its section and its priority (deb) |
77
+ | `mac.targets` | `pkg`, `tar.gz` | The macOS installers |
78
+ | `win.targets` | `nsis`, `msi`, `zip` | The Windows installers |
79
+ | `linux.scripts`, `mac.scripts`, `win.scripts`… | None | The scripts of the installers (section 5) |
80
+ | `electron` | None | The configuration of electron-builder (section 8) |
81
+
82
+ `lakutata.icon` (beside `lakutata.installer`) is a square PNG image of 256 pixels or more: the icon of the Windows
83
+ executable and installers, and of the Electron applications.
84
+
85
+ The configuration is checked before anything is built, the options of the installers that are not made included, and an
86
+ error names the field:
87
+
88
+ ```
89
+ error: The name of the package "Bad Name" must be lower case letters, digits, "+", "-" and "." (lakutata.installer.name)
90
+ error: The installer msi is not one of macOS (pkg, tar.gz)
91
+ error: The script lakutata.installer.mac.scripts.postinstall (scripts/missing.sh) does not exist
92
+ ```
93
+
94
+ ## 3. What is installed where
95
+
96
+ | | Linux | macOS | Windows |
97
+ |---|---|---|---|
98
+ | The application | `/opt/<name>` | `/usr/local/lib/<name>` | `C:\Program Files\<name>` |
99
+ | Its program in the PATH | `/usr/bin/<program>` | `/usr/local/bin/<program>` | the PATH of the machine |
100
+ | Its service | `/usr/lib/systemd/system/<service>.service`; `/etc/init.d/<service>` (OpenRC, Alpine) | `/Library/LaunchDaemons/<appId>.plist` | a Windows service: `<name>-service.exe` (WinSW) next to the program |
101
+ | The environment of the service | `/etc/<name>/<name>.env` | `/usr/local/etc/<name>/<name>.env` | `C:\ProgramData\<name>\<name>.env` |
102
+ | Its working directory (its data) | `/var/lib/<name>` | `/usr/local/var/lib/<name>` | `C:\ProgramData\<name>` |
103
+ | Its logs | the journal of systemd (`journalctl -u <service>`); `/var/log/<name>.log` with OpenRC | `/usr/local/var/log/<name>.log` | `C:\ProgramData\<name>\logs` |
104
+ | Removal | by the package manager | `sudo /usr/local/lib/<name>/uninstall` | like other Windows programs |
105
+
106
+ - **macOS does not remove packages**: the `uninstall` script installed with the application removes it, its service and
107
+ its link in the PATH.
108
+ - **The removal keeps** the data, the environment file and the user of the service, so that a new installation finds
109
+ them again.
110
+ - The archives (`tar.gz`, `zip`) only hold the application: they register no service and run no script.
111
+
112
+ ## 4. The service
113
+
114
+ With `service` (`true`, or its options), the application runs when the system starts: under systemd, OpenRC on Alpine,
115
+ launchd on macOS, or as a Windows service (WinSW). The installation enables and starts it, an upgrade restarts it, and
116
+ the removal stops and disables it first.
117
+
118
+ ```json
119
+ {
120
+ "lakutata": {
121
+ "installer": {
122
+ "service": {
123
+ "args": ["serve"],
124
+ "env": {"PORT": "8080", "LOG_LEVEL": "info"},
125
+ "restart": "on-failure"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ | Option | Default | Meaning |
133
+ |---|---|---|
134
+ | `name` | `name` | The name of the service (systemd, OpenRC, Windows); on macOS the service is named by `appId` |
135
+ | `description` | The description of the package | |
136
+ | `args` | None | The arguments of the program |
137
+ | `env` | `MODE=production` | Its environment, added to `MODE=production` |
138
+ | `user` | A system user created by the installation: `<name>` on Linux, `_<name>` (hidden) on macOS; on Windows the virtual account `NT SERVICE\<service>` | The user it runs as; `root` (Linux, macOS), or `LocalService`, `NetworkService`, `LocalSystem` on Windows |
139
+ | `restart` | `on-failure` | Restart it when it fails (`on-failure`), whenever it ends (`always`), or never (`no`); on Windows and with OpenRC, `on-failure` and `always` behave alike |
140
+ | `autoStart` | `true` | Enable and start it at the installation; otherwise the administrator enables and starts it (`sudo systemctl enable --now <service>`; on macOS, `sudo launchctl enable system/<appId>`, then `sudo launchctl bootstrap system /Library/LaunchDaemons/<appId>.plist`) |
141
+
142
+ - **The environment file** holds `env`. Administrators can change it, and upgrades and removals keep their changes. The
143
+ service reads it when it starts, so restart the service after changing it (`sudo systemctl restart <service>`,
144
+ `sudo rc-service <service> restart`, `sudo launchctl kickstart -k system/<appId>`, `Restart-Service <service>`).
145
+ - **The working directory** of the service belongs to its user: write the data of the application there, with
146
+ relative paths or `process.cwd()`.
147
+ - **Stopping** the service ends the application gracefully: it receives `SIGTERM`, or on Windows a Ctrl+C, which Node.js
148
+ receives as `SIGINT`. systemd, launchd and WinSW give it 30 seconds to end.
149
+
150
+ ## 5. Installer scripts
151
+
152
+ The installers can run scripts of the application, files of the project: to create a database, migrate data, or
153
+ clean up. They are given for a platform (run by all its installers), for one installer (replacing the platform's
154
+ script of the same moment), as native scripts of a Linux package, or as an NSIS file:
155
+
156
+ ```json
157
+ {
158
+ "lakutata": {
159
+ "installer": {
160
+ "linux": {
161
+ "scripts": {"postinstall": "build/linux/postinstall.sh", "preremove": "build/linux/preremove.sh"},
162
+ "rpm": {"scripts": {"postinstall": "build/linux/rpm-postinstall.sh"}},
163
+ "deb": {"nativeScripts": {"postinstall": "build/linux/postinst.sh"}}
164
+ },
165
+ "mac": {"scripts": {"postinstall": "build/mac/postinstall.sh"}},
166
+ "win": {"scripts": {"postinstall": "build/win/postinstall.ps1"}, "nsis": {"include": "build/win/installer.nsh"}}
167
+ }
168
+ }
169
+ }
170
+ ```
171
+
172
+ | Script | When it runs |
173
+ |---|---|
174
+ | `preinstall` | Before the files of the application are installed |
175
+ | `postinstall` | After they are installed, before the service starts |
176
+ | `preremove` | Before the removal, once the service is stopped |
177
+ | `postremove` | After the files are removed |
178
+
179
+ ```sh
180
+ #!/bin/sh
181
+ # build/linux/postinstall.sh: $1 is install or upgrade
182
+ if [ "$1" = install ]; then
183
+ echo "Installed in $LAKUTATA_INSTALL_DIR"
184
+ fi
185
+ ```
186
+
187
+ - **Their argument** (`$1`, `$args[0]` in PowerShell) says what the installer does: `install` or `upgrade` for
188
+ `preinstall` and `postinstall`, `remove` for `preremove` and `postremove` (an upgrade runs no removal script). Each
189
+ package manager signals an upgrade in its own way (dpkg, rpm, apk, pacman): the installers translate it.
190
+ - **`LAKUTATA_INSTALL_DIR`** is the directory of the application.
191
+ - **They run as root**; on Windows, as an administrator with NSIS and as the system account with Windows Installer.
192
+ When a script fails, the installation (or the removal) fails.
193
+ - **Linux and macOS**: the scripts run with the interpreter of their first line (`#!/bin/bash`), `/bin/sh` by default.
194
+ `linux.<package>.nativeScripts` run as the scripts of the package itself, with the arguments of the package manager
195
+ (and `preupgrade`, `postupgrade` for apk and archlinux). On macOS, the `uninstall` script runs the removal scripts.
196
+ - **Windows**: the scripts are PowerShell files (`.ps1`), run by Windows PowerShell. Windows Installer passes each one
197
+ on a command line, which limits it to about 23 KB. `win.nsis.include` is an NSIS file (`.nsh`) that may define the
198
+ macros `customHeader`, `customInit`, `customInstall` (at the end of the installation), `customUnInit` and
199
+ `customUnInstall` (at the start of the removal).
200
+ - **The archives** run no script: the tool warns when a script would not run in one of the installers it makes
201
+ (`! Some scripts of the application are not run by their installers: tar.gz (postinstall)`).
202
+
203
+ ## 6. Signing
204
+
205
+ - **macOS**: the `pkg` installers are not signed, and the program they install is signed ad hoc only. The Developer ID
206
+ signature and the notarization, which distribution outside the machines of the organization needs, are not made yet.
207
+ - **Windows**: the NSIS and MSI installers are not signed: sign them with signtool before distributing them. The program
208
+ made with Node.js keeps the signature of `node.exe`, which no longer matches, and the tool warns of it.
209
+ - **Linux**: the packages are not signed.
210
+
211
+ ## 7. Other platforms
212
+
213
+ The installers of the other platforms are made on them: a CI with a machine per platform, or, for Linux, a container
214
+ of the architecture. The templates with the `installer` feature have both scripts:
215
+
216
+ ```json
217
+ {
218
+ "scripts": {
219
+ "package:host": "npm run build && npm run package -w app -- --outdir ../../dist/installer",
220
+ "package:linux": "docker buildx build --file Dockerfile.installer --output type=local,dest=dist/installer ."
221
+ }
222
+ }
223
+ ```
224
+
225
+ `npm run package:linux` makes the Linux packages in a container (on macOS and Windows too) and copies them to
226
+ `dist/installer`; `docker buildx build --platform linux/arm64 ...` makes those of another architecture. Its
227
+ `Dockerfile.installer`:
228
+
229
+ ```dockerfile
230
+ FROM node:22-bookworm AS build
231
+ WORKDIR /project
232
+ COPY . .
233
+ RUN (npm ci --no-audit --no-fund || npm install --no-audit --no-fund) && npm run build && npm run package -w app -- --outdir /installer
234
+
235
+ FROM scratch
236
+ COPY --from=build /installer /
237
+ ```
238
+
239
+ The `apk` packages are made the same way on `node:22-alpine`, with Bun when the application has native addons (the
240
+ executables of Node.js do not load them on musl). A CI matrix makes the rest, for example with GitHub Actions:
241
+
242
+ ```yaml
243
+ jobs:
244
+ installers:
245
+ strategy:
246
+ matrix:
247
+ os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
248
+ runs-on: ${{ matrix.os }}
249
+ steps:
250
+ - uses: actions/checkout@v4
251
+ - uses: actions/setup-node@v4
252
+ with:
253
+ node-version: 22
254
+ - run: npm ci
255
+ - run: npm run package:host
256
+ - uses: actions/upload-artifact@v4
257
+ with:
258
+ name: installers-${{ runner.os }}-${{ runner.arch }}
259
+ path: dist/installer/
260
+ ```
261
+
262
+ ## 8. Electron applications
263
+
264
+ With `"runtime": "electron"`, the installers are made by electron-builder, which the project installs with `electron`.
265
+ The main process is bundled (Electron left out), and its native addons and resources (`lakutata.resources`: the
266
+ renderer and the preload script, read through `resourcesPath()`) go into the resources of Electron. The `electron-react`
267
+ and `electron-vue` templates are configured this way.
268
+
269
+ - **The installers** are targets of electron-builder: `deb` on Linux, `dmg` on macOS and `nsis` on Windows by default
270
+ (`linux.targets`, `mac.targets`, `win.targets`). The name of the files is the name of the package, without its scope.
271
+ - **`electron`** is merged into the configuration of electron-builder: icons, file associations, signing, updates.
272
+ `name`, `appId`, `productName`, the description, the maintainer, the homepage and the license are given to it too.
273
+ - **An Electron application is not a service**: it runs in the session of its user, and `service` is refused.
274
+ - **The scripts** run in the `deb` and `rpm` packages (all four), the `pkg` (`preinstall`, `postinstall`) and the
275
+ `nsis` installer (`preinstall`, `postinstall`, `preremove`); the other targets run none.
276
+ - **Native addons** are copied as they are installed: electron-builder does not rebuild them. The addons built on
277
+ Node-API run as they are; install the Electron builds of the others before packing (`electron-rebuild`)
278
+ ([NativeAddons](NativeAddons.md), section 4).
279
+ - On Linux arm64, the deb and rpm packages need the fpm of the system (`gem install fpm`, `USE_SYSTEM_FPM=true`), as the
280
+ `Dockerfile.installer` of the Electron templates installs it.
@@ -0,0 +1,206 @@
1
+ # Lakutata Logging
2
+
3
+ Every application of `lakutata` has a logger: the `log` component, preset by the package (`Logger`, from
4
+ `lakutata/com/logger`). It writes readable lines, or the JSON lines of pino for a log collector, to the standard output
5
+ or to other streams. The framework logs through it too.
6
+
7
+ ## 1. The `log` component
8
+
9
+ ```typescript
10
+ import {Application, Component} from 'lakutata'
11
+ import {Inject} from 'lakutata/decorator/di'
12
+ import {Logger} from 'lakutata/com/logger'
13
+
14
+ class Orders extends Component {
15
+ @Inject('log')
16
+ protected readonly log: Logger
17
+
18
+ public pay(orderId: number, amount: number): void {
19
+ this.log.info({orderId: orderId}, 'order %d paid: %s', orderId, amount.toFixed(2))
20
+ }
21
+ }
22
+
23
+ Application
24
+ .run(() => ({
25
+ id: 'shop.app',
26
+ name: 'Shop',
27
+ components: {
28
+ //The options of the preset logger: no class needed
29
+ log: {level: 'info', pretty: process.env.NODE_ENV !== 'production'},
30
+ orders: {class: Orders}
31
+ }
32
+ }))
33
+ .onLaunched(async (app: Application): Promise<void> => {
34
+ const orders: Orders = await app.getObject<Orders>('orders')
35
+ orders.pay(42, 9.9)
36
+ })
37
+ ```
38
+
39
+ - Inject it by its name, `log`. Type it as `Logger`, or as `ILogger` (from `lakutata`), the interface that every
40
+ logger of an application implements.
41
+ - The handlers of the application (`onLaunched`, `onDone`, `onUncaughtException`, `onFatalException`) receive it as
42
+ their second argument (see [Application](Application.md), section 2).
43
+ - Its options go into the `log` entry of `components`, without `class` (section 4).
44
+
45
+ ## 2. Writing a line
46
+
47
+ | Method | Level | For |
48
+ |---|---|---|
49
+ | `fatal()` | 60 | A failure the application cannot go on after; only `Logger` has it, not `ILogger` |
50
+ | `error()` | 50 | A failure that someone must act on |
51
+ | `warn()` | 40 | An unexpected event the application recovers from |
52
+ | `info()` | 30 | The normal course of the application |
53
+ | `debug()` | 20 | The details for debugging |
54
+ | `trace()` | 10 | Finer details |
55
+
56
+ Each method takes an optional object, whose properties are added to the line, then a message, then the values of its
57
+ placeholders:
58
+
59
+ ```typescript
60
+ import {Logger} from 'lakutata/com/logger'
61
+
62
+ declare const log: Logger
63
+
64
+ //%s string, %d and %f number, %i integer, %o, %O and %j JSON, %% a percent sign
65
+ log.info('user %s signed in from %s', 'alice', '10.0.0.7')
66
+ //The properties of the object are added to the line
67
+ log.warn({orderId: 42, retries: 3}, 'payment retried %d times', 3)
68
+ //An error: written under "err"; its message is the message of the line when none is given
69
+ log.error(new Error('connection refused'), 'payment failed')
70
+ ```
71
+
72
+ - A value without a placeholder is dropped: `log.info('done', 42)` writes `done`.
73
+ - An error is written under `err`: its type, its message followed by the messages of its causes, its stack followed by
74
+ the stacks of its causes, and its own properties (such as the `errno` and `statusCode` of an exception).
75
+ - An HTTP request is written under `req` (method, URL, headers, remote address), an HTTP response under `res`. Their
76
+ `Authorization`, `Cookie`, `Set-Cookie` and `Proxy-Authorization` headers are written as `[Redacted]`.
77
+ - Nothing else is redacted, and there are no child loggers: log the fields you need, not tokens or whole objects that
78
+ hold them, and give the context (a request id) in the object of each call.
79
+
80
+ ```text
81
+ [13:44:12.447] ERROR (Shop/24236): payment failed
82
+ err: {
83
+ "type": "PaymentFailedException",
84
+ "message": "Payment of order 7 failed: socket hang up",
85
+ "stack":
86
+ PaymentFailedException: Payment of order 7 failed
87
+ at pay (file:///app/build/Orders.js:48:17)
88
+ caused by: Error: socket hang up
89
+ at charge (file:///app/build/Gateway.js:12:9)
90
+ "appId": "shop.app",
91
+ "appName": "Shop",
92
+ "statusCode": 502,
93
+ "errMsg": "Payment of order 7 failed",
94
+ "err": "PaymentFailedException",
95
+ "errno": "E_PAYMENT_FAILED"
96
+ }
97
+ ```
98
+
99
+ ## 3. Readable lines or JSON
100
+
101
+ By default (`pretty: true`), the lines are readable, with the time in the local time zone, the level, the name of the
102
+ application, its process id, the message, then one indented line per property:
103
+
104
+ ```text
105
+ [13:45:04.246] INFO (Shop/24297): order 42 paid: 9.90
106
+ orderId: 42
107
+ ```
108
+
109
+ With `pretty: false`, each line is a JSON object, in the format of pino: `level`, `time` (milliseconds since the
110
+ epoch), `pid`, `hostname`, `name` (the name of the application), the properties of the object, and `msg`. A log
111
+ collector reads these lines; use them in production.
112
+
113
+ ```text
114
+ {"level":30,"time":1792215904479,"pid":24300,"hostname":"api-1","name":"Shop","orderId":42,"msg":"order 42 paid: 9.90"}
115
+ ```
116
+
117
+ ## 4. Options
118
+
119
+ | Option | Default | Meaning |
120
+ |---|---|---|
121
+ | `level` | `'trace'` | The lowest level written: `'trace'`, `'debug'`, `'info'`, `'warn'`, `'error'`, `'fatal'` (only the lines of `fatal()`) or `'silent'` (nothing) |
122
+ | `pretty` | `true` | Readable lines; `false` writes the JSON lines |
123
+ | `colorize` | on a terminal | Colors the readable lines written to the standard output |
124
+ | `destinations` | `[process.stdout]` | The streams that receive the lines (section 5) |
125
+ | `sync` | `false` | Writes each line at once, instead of in batches (section 6) |
126
+ | `redactedHeaders` | `['authorization', 'cookie', 'set-cookie', 'proxy-authorization']` | The headers written as `[Redacted]`; the list replaces the default one, and `[]` redacts none |
127
+
128
+ By default, the readable lines are colored when the standard output is a terminal and the `NO_COLOR` environment
129
+ variable is not set: a standard output redirected to a file or read by a collector gets no escape codes.
130
+ `colorize: true` colors it in any case.
131
+
132
+ ## 5. Destinations
133
+
134
+ Each stream of `destinations` receives every line:
135
+
136
+ ```typescript
137
+ import {createWriteStream} from 'node:fs'
138
+ import {Application} from 'lakutata'
139
+
140
+ Application.run(() => ({
141
+ id: 'shop.app',
142
+ name: 'Shop',
143
+ components: {
144
+ log: {
145
+ pretty: false,
146
+ //The standard output, and a file the lines are appended to
147
+ destinations: [process.stdout, createWriteStream('shop.log', {flags: 'a'})]
148
+ }
149
+ }
150
+ }))
151
+ ```
152
+
153
+ - The logger does not close the streams, and does not rotate the files: leave that to the system (logrotate, the
154
+ collector of the containers).
155
+ - An application whose standard output belongs to a protocol (JSON-RPC over stdio, MCP over stdio) writes its logs to
156
+ the standard error: `log: {class: Logger, destinations: [process.stderr]}` (see [ServiceCalls](ServiceCalls.md),
157
+ section 4).
158
+
159
+ ## 6. Buffering
160
+
161
+ The lines are written in batches: at the next turn of the event loop, or once 16 KB are waiting. The waiting lines are
162
+ also written when the process exits and when the logger is destroyed.
163
+
164
+ - `sync: true` writes each line at once.
165
+ - `flush()` writes the waiting lines now, such as before a `process.exit()`.
166
+ - `fatal()` writes its line and the waiting ones at once.
167
+ - The standard output, the standard error and the files are written synchronously, so the lines keep their order.
168
+
169
+ ## 7. Another logger
170
+
171
+ The `log` component can be replaced by a class of your own: a component that implements `ILogger`, declared with
172
+ `class`. The framework and the handlers of the application then log through it.
173
+
174
+ ```typescript
175
+ import {Application, Component, type ILogger} from 'lakutata'
176
+
177
+ class ConsoleOnlyLogger extends Component implements ILogger {
178
+ public error(...args: unknown[]): void {
179
+ console.error(...args)
180
+ }
181
+
182
+ public warn(...args: unknown[]): void {
183
+ console.warn(...args)
184
+ }
185
+
186
+ public info(...args: unknown[]): void {
187
+ console.info(...args)
188
+ }
189
+
190
+ public debug(...args: unknown[]): void {
191
+ console.debug(...args)
192
+ }
193
+
194
+ public trace(...args: unknown[]): void {
195
+ console.debug(...args)
196
+ }
197
+ }
198
+
199
+ Application.run(() => ({
200
+ id: 'tool.app',
201
+ name: 'Tool',
202
+ components: {
203
+ log: {class: ConsoleOnlyLogger}
204
+ }
205
+ }))
206
+ ```