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,315 @@
1
+ # Upgrading from Lakutata 2.x
2
+
3
+ This page brings a project from Lakutata 2.x (the last release is 2.0.126) to 3.x: how to tell the versions apart, the
4
+ steps, the renamed and moved APIs, the behavior that changes without a compile error, and how to check the result.
5
+
6
+ It is for the projects that depend on the package `lakutata` at version 2. A project on Lakutata 1.x depends instead on
7
+ `@lakutata/core` at version 1 and on `@lakutata-component/*` or `@lakutata-module/*` packages: it follows
8
+ [UpgradingFrom1](UpgradingFrom1.md), not this page.
9
+
10
+ ## 1. Telling 2.x from 3.x
11
+
12
+ | Sign | 2.x | 3.x |
13
+ |---|---|---|
14
+ | `lakutata` in `package.json` | `^2.…` | `^3.…` |
15
+ | `node_modules/lakutata/package.json` | No `dependencies` (everything bundled) | Depends on `@lakutata/core`, `@lakutata/entrypoint`… at its own version |
16
+ | Helpers | `import {Delay} from 'lakutata/helper'` | `import {delay} from 'lakutata/utils'` |
17
+ | Exported functions | PascalCase: `BuildEntrypoints`, `BuildDatabaseOptions`, `MD5` | camelCase: `buildEntrypoints`, `buildDatabaseOptions`, `md5` |
18
+ | Transports | Written by hand: `BuildHTTPEntrypoint((module, routeMap, handler, registerDestroy) => …)` | Adapters: `expressHTTPEntrypoint()` from `lakutata/adapter/express`… |
19
+ | Subpaths of 3.x only | | `lakutata/utils`, `lakutata/security`, `lakutata/adapter/*`, `lakutata/com/{containerd,mutex,queue,i18n}` |
20
+ | Node.js | `>=20` | `^22.13.0 \|\| >=24.11.0` |
21
+
22
+ `npx @lakutata/cli info` prints the versions of the tool, of the framework and of Node.js, and the `lakutata` version
23
+ that the project declares and the one installed. There is no upgrade command: the steps below are done by hand.
24
+
25
+ ## 2. The steps
26
+
27
+ - **Node.js** `^22.13.0 || >=24.11.0` (22.13 or a later 22.x, or 24.11 and later; not 23). Update `engines`, the CI
28
+ matrix and the base images of the Dockerfiles.
29
+ - **A TypeScript that knows `"module": "Node20"`** (the templates use TypeScript 6): `lakutata/tsconfig.json` now sets
30
+ it (2.x: `Node16`), and older compilers reject it.
31
+ - **`package.json`**: `"type": "module"`, `lakutata` `^3.0.0`, every `@lakutata/*` package at the same version (they
32
+ depend on each other's exact version), and `@lakutata/cli` in `devDependencies`.
33
+ - **A clean install**: remove `node_modules` and the lock file, then install. Upgrading in place can leave several
34
+ copies of `@lakutata/core`, whose classes and decorator metadata do not match (`instanceof` and injection fail across
35
+ them). `npm ls @lakutata/core` must print one version.
36
+ - **`tsconfig.json`** extends `lakutata/tsconfig.json` and sets its own `rootDir`, `outDir` and `include`. Keep
37
+ `include`: the published options set `allowJs`, so without it a configuration file at the root of the project
38
+ (`eslint.config.js`) is compiled too. A project that keeps its own options needs `experimentalDecorators`,
39
+ `emitDecoratorMetadata` and `useDefineForClassFields: false`.
40
+
41
+ ```json
42
+ {
43
+ "extends": "lakutata/tsconfig.json",
44
+ "compilerOptions": {
45
+ "rootDir": "src",
46
+ "outDir": "build"
47
+ },
48
+ "include": ["src"]
49
+ }
50
+ ```
51
+
52
+ - **Relative imports** end in `.js` (`import {config} from './config/Config.js'`), as in the ES module projects of 2.x.
53
+ - **The libraries of the features used** are optional peer dependencies of `lakutata`, which the project installs (a
54
+ 2.x project already has those that its own transports import); see [GettingStarted](GettingStarted.md), section 3:
55
+
56
+ | Used | Install |
57
+ |---|---|
58
+ | `lakutata/adapter/express` | `express` (4.21+ or 5), `@types/express` |
59
+ | `lakutata/adapter/fastify` | `fastify` 5 |
60
+ | `lakutata/adapter/commander` | `commander` (12 to 15); `@inquirer/prompts` (7 or 8) for the prompts |
61
+ | `lakutata/adapter/socket.io` | `socket.io` 4 |
62
+ | `lakutata/adapter/jsonrpc` over WebSocket | `ws` 8 |
63
+ | A database `type` | `pg`, `mysql2` (3.15.3+), `better-sqlite3` 12 (instead of `sqlite3`), `mongodb` 7, `mssql` 12, `oracledb`, `sql.js`… |
64
+ | A cache store | `redis` (4 or 5) or `@redis/client`, `memjs`, `mongodb`, `mysql2`, `pg`, `better-sqlite3` (the SQLite store used `sqlite3` in 2.x) |
65
+ | NATS, D-Bus | `@lakutata/nats`, `@lakutata/dbus`, at the version of `lakutata` |
66
+
67
+ - **Rename** with the tables of section 3, then build: none of the renamed functions has an alias, so the compiler
68
+ reports every import left. Then go through section 4, which the compiler does not report.
69
+
70
+ ## 3. Renamed and moved
71
+
72
+ | 2.x | 3.x |
73
+ |---|---|
74
+ | `lakutata/helper` | `lakutata/utils` (the same helpers, renamed below, plus `Time`, `cloneDeep`, `isSubclassOf`) |
75
+ | `Paginator`, `PaginatorHandlerResult` from `lakutata/helper` | `paginator`, `PaginatorHandlerResult` from `lakutata/dtos` |
76
+ | `IPatRun` from `lakutata` | Removed: the pattern matching is internal |
77
+
78
+ The other subpaths of 2.x keep their names: `lakutata`, `lakutata/orm`, `lakutata/dtos`,
79
+ `lakutata/com/{entrypoint,database,cacher,docker,monitor,logger}`, `lakutata/provider/{database,passwordHash}`,
80
+ `lakutata/decorator/{di,dto,ctrl,asst,orm}` and `lakutata/tsconfig.json`.
81
+
82
+ Every exported function is in camelCase, and the 2.x names have no alias. Most of them only lower their first letter
83
+ (`ArrayToSet` becomes `arrayToSet`):
84
+
85
+ - `lakutata/utils`: `arrayToSet`, `convertArrayLikeToIterable`, `convertArrayLikeToStream`, `delay`,
86
+ `getObjectNestingDepth`, `getObjectPropertyPaths`, `hexToIEEE754`, `hexToSigned`, `hexToUnsigned`, `isAbortError`,
87
+ `isEmptyObject`, `isGlobString`, `isNativeFunction`, `isPath`, `isPromise`, `isPromiseLike`, `isSymbol`, `isXML`,
88
+ `mergeArray`, `mergeMap`, `mergeSet`, `noCase`, `objectConstructor`, `objectHash`, `objectParentConstructor`,
89
+ `objectParentConstructors`, `objectPrototype`, `objectToMap`, `randomString`, `setToArray`, `signedToHex`,
90
+ `sortArray`, `sortKeys`, `sortObject`, `templating`, `uniqueArray`, `unsignedToHex`;
91
+ - `lakutata/com/entrypoint`: `buildEntrypoints`, `buildHTTPEntrypoint`, `buildCLIEntrypoint`, `buildServiceEntrypoint`;
92
+ - `lakutata/com/cacher`: `buildCacherOptions`; `lakutata/com/database` and `lakutata/provider/database`:
93
+ `buildDatabaseOptions`;
94
+ - `lakutata/com/docker`: `buildDockerConnectionOptions`, `buildDockerSocketConnectionOptions`,
95
+ `buildDockerHttpConnectionOptions`, `buildDockerHttpsConnectionOptions`, `buildDockerSSHConnectionOptions`,
96
+ `parseEnvToRecord`, `parseRepositoryTag`.
97
+
98
+ `As`, `DevNull` and `Glob` (with the new `GlobSync`) stay in PascalCase, as do the classes (`Time`, `URLBuilder`,
99
+ `UUID`, `ObjectPath`, `MessagePack`, `Statistics`, `IP`, `IPv4`, `IPv6`) and the decorators. The other renames:
100
+
101
+ | 2.x | 3.x |
102
+ |---|---|
103
+ | `GraceExit` | `gracefulExit` |
104
+ | `IsExists` | `pathExists` |
105
+ | `IsHtml` | `isHTML` |
106
+ | `NonceStr` | `nonceString` |
107
+ | `MD5`, `SHA1`, `SHA256` | `md5`, `sha1`, `sha256` |
108
+ | `IEEE754ToHex` | `ieee754ToHex` |
109
+ | `Split`, `ToLower`, `ToUpper` | Removed: `noCase(input).split(' ')` gives the words in lower case (`['']` for an input without words) |
110
+ | Type `SortOptions` | `SortArrayOptions` |
111
+ | Types `IPv4SubNet`, `IPv6SubNet` | `IPv4Subnet`, `IPv6Subnet` |
112
+ | Field `comperssed` of `IPv6Result` | `compressed` |
113
+ | `AccessDenyException` | `AccessDeniedException`: its `errno` is still `E_ACCESS_DENY`, its `err` (the class name) changes |
114
+ | Type `OnDelEventData` (`lakutata/com/cacher`) | `OnDeleteEventData`; the event is still `'del'` |
115
+ | `@Lifetime('APPLICATION_SINGLETON')`, `'MODULE_SINGLETON'` | `@Singleton()`: the lifetimes are `SINGLETON`, `SCOPED` and `TRANSIENT` |
116
+ | Static `databaseSymbol` of `Database` | Removed |
117
+
118
+ The ORM moves from TypeORM 0.3.24 to TypeORM 1.1.2, whose source is part of `@lakutata/orm` (there is no `typeorm`
119
+ package):
120
+
121
+ | 2.x | 3.x |
122
+ |---|---|
123
+ | `type: 'sqlite'` (driver `sqlite3`) | `type: 'better-sqlite3'` (driver `better-sqlite3`) |
124
+ | `PostgresConnectionOptions`, `MysqlConnectionOptions`… | `PostgresDataSourceOptions`, `MysqlDataSourceOptions`… (the old names are deprecated aliases) |
125
+ | `SqliteConnectionOptions` | `BetterSqlite3DataSourceOptions` |
126
+ | `AbstractRepository` | Removed: a `Repository`, or `repository.extend({...})` |
127
+ | `relations: ['author']`, `select: ['id']` | `relations: {author: true}`, `select: {id: true}` |
128
+ | `import {In} from 'typeorm'` | `import {In} from 'lakutata/orm'`: a `typeorm` package would be another copy |
129
+
130
+ The packages published apart from `lakutata` in 2.x are renamed the same way:
131
+
132
+ | 2.x | 3.x |
133
+ |---|---|
134
+ | `SetupNatsServiceEntrypoint` (`@lakutata/nats`) | `natsServiceEntrypoint` |
135
+ | `BuildServiceProxy`, `JSONCodec`, `MessagePackCodec` | `buildServiceProxy`, `jsonCodec`, `messagePackCodec` |
136
+ | `CreateDBusError` (`@lakutata/dbus`) | `createDBusError` |
137
+ | D-Bus client events `online`, `offline`, `replaced`, `NameOwnerChanged`, `NameAcquired`, `NameLost` | `serviceOnline`, `serviceOffline`, `serviceReplaced`, `nameOwnerChanged`, `nameAcquired`, `nameLost` (a listener of an old name is never called) |
138
+ | D-Bus client options `advancedResponse`, `convertBigIntToNumber` | `decode`: `{types: 'all'}` keeps the D-Bus types, `{int64: 'bigint'}` the bigints |
139
+
140
+ ## 4. Behavior that changes without a compile error
141
+
142
+ ### The application and its objects
143
+
144
+ See [Application](Application.md) and [Objects](Objects.md).
145
+
146
+ - **The mode** (`app.mode()`) is the `MODE` environment variable, else `NODE_ENV`, else the `mode` option, else
147
+ `development`, written back to both variables. 2.x took the option (`development` by default) and overwrote
148
+ `NODE_ENV` with it: a deployment that sets `NODE_ENV=production` now runs in production, whatever the option says.
149
+ - **SIGTERM and SIGINT** end the application gracefully (`onDone` runs, the entrypoints stop, the objects are destroyed,
150
+ the exit code is 0); 2.x left them to Node.js. Remove the application's own `process.on('SIGTERM', …)`, or set
151
+ `shutdownSignals: false`. The `onDone` handlers receive a third argument, `{code, signal?}`.
152
+ - **A process with nothing left to do** (no server, connection or timer) ends the application gracefully: the `onDone`
153
+ handlers and the `destroy()` hooks run then too. In 2.x the process just ended.
154
+ - **A failed launch** exits with code 1 when there is no `onFatalException` handler. 2.x logged the error, and the
155
+ process kept running or ended with code 0.
156
+ - **The options are checked at launch** (`checkOptions`, on by default): an invalid value of a `@Configurable` property
157
+ fails the launch with an `InvalidObjectOptionsException` (`E_INVALID_OBJECT_OPTIONS`) that lists every invalid
158
+ option, even for an object never created; an option that is not a `@Configurable` property produces a warning. `''`
159
+ fails a required string: write `process.env.X || undefined`, not `|| ''`. A class that accepts other options declares
160
+ a static `acceptsOption(name, value)`.
161
+ - **The initializer of a `@Configurable` property is its default** (`protected readonly port: number = 8080`), and it
162
+ is validated like a configured value. 2.x replaced it with the default of the schema, or `undefined`.
163
+ - **New lifecycle hooks**: `startup()` (after `init()`, before the object is handed out), `launched()` (once the
164
+ application has launched), `beforeDestroy()` (when the teardown starts), and the static `acceptsOption()`. A 2.x class
165
+ with a member of one of these names is now called by the framework: rename the member.
166
+ - **A `Component` is locked to the singleton lifetime**: `@Transient()` or `@Scoped()` on a subclass throws a
167
+ `LifetimeLockedException` when the class is defined (2.x accepted it). Make it a `Provider`, which is scoped by
168
+ default and may be `@Singleton()` or `@Transient()`.
169
+ - **A built-in component declared with `class`** (`components: {log: {class: MyLogger}}`) replaces the preset one; 2.x
170
+ silently kept the preset. A declaration without `class` still configures the preset `log` or `entrypoint`.
171
+ - **A glob of objects** (`objects.anonymous`, `container.load()`) registers every exported `BaseObject` subclass of each
172
+ file (2.x: only the first export), and a file that fails to import fails the launch (2.x skipped it).
173
+
174
+ ### Errors and values
175
+
176
+ - **Statuses**: `AccessDeniedException` has the `statusCode` 403, `ControllerActionNotFoundException` 404 and
177
+ `InvalidValueException` 400. All three had 500 in 2.x, so clients and tests that check for 500 change.
178
+ - **Messages**: an exception created without arguments takes the words of its class name (`Order not found exception`;
179
+ 2.x gave `Ordernotfoundexception`); a single parameter fills `{0}` (`new OrderException('Order {0}', 7)`); the `%`
180
+ placeholders follow `util.format()` (`%s %d %i %f %j %o %O %c %%`), and a `%` before another character is kept.
181
+ - **DTOs are strict, as in 2.x**: the HTTP adapters give the query and the route parameters as strings, so a number or
182
+ a boolean read from them needs `.strict(false)`. `PaginationSearchDTO` now converts its `limit` and `offset`.
183
+ - **`Time`**: a date without a time (`new Time('2024-03-01')`) is midnight in the default time zone (2.x: UTC).
184
+ - **`PasswordHash`**: `hash()` throws a `PasswordTooLongException` above 72 bytes of UTF-8, and `validate()` returns
185
+ `false` for such a password. 2.x hashed its first 72 bytes, so those users can no longer log in. `saltRounds` is
186
+ between 4 and 31.
187
+ - **`Glob`** takes only `cwd`, `absolute`, `dot`, `nodir`, `follow`, `ignore` and `nocase`, and returns a sorted
188
+ `string[]` without duplicates: `withFileTypes` and the extended patterns (`+(a|b)`) are gone.
189
+
190
+ ### The entrypoint
191
+
192
+ See [HTTP](HTTP.md), section 8, and [ServiceCalls](ServiceCalls.md).
193
+
194
+ - **The transport functions of 2.x still work** under `http`, `cli` and `service` (renamed `buildHTTPEntrypoint`…).
195
+ They are called when the entrypoint starts and their promise is awaited: the launch waits for the server, a rejection
196
+ (a port in use) fails it, and a function that awaits a long-running command (`await program.parseAsync()`) holds the
197
+ launch until the command ends. Call it without `await`, or move to `commanderCLIEntrypoint()`.
198
+ - **Moving to an adapter**: the middlewares and routes of an Express server written by hand go into the `before` hook of
199
+ `expressHTTPEntrypoint()`, its body limit into `body`, the input it built into `input`. The responses change: the HTTP
200
+ adapters answer an error with its status and `{errno, errMsg}`, and `jsonEnvelope({actionErrorStatus: 200, extra})`
201
+ gives the `{errno, errMsg, data}` envelope that 2.x applications often wrote themselves (example below). The service
202
+ adapters (HTTP service, Socket.IO, JSON-RPC, Electron) answer `{success, error, payload}`.
203
+ - **Nested service patterns are compared key by key**: `@ServiceAction({res: {name: 'user'}, act: 'get'})` matches that
204
+ nested value only. 2.x compared any object as `[object Object]`, so two actions that differed by a nested value
205
+ replaced each other.
206
+ - **`Controller.context`** is `CLIContext | HTTPContext | ServiceContext | BaseContext`: a function whose parameter is
207
+ the 2.x union no longer accepts `this.context`. Add `BaseContext`, or narrow it with `instanceof`.
208
+ - **Electron**: Lakutata 2.x had no Electron transport. 3.x serves the actions in the main process with
209
+ `electronIPCServiceEntrypoint()` from `lakutata/adapter/electron`: the preload script calls
210
+ `ipcRenderer.invoke('lakutata:service', message, clientId)`, answered with `{success, error, payload}`, and the events
211
+ come from `entrypoint.notify(clientId, event, ...args)` and `entrypoint.broadcast(event, ...args)`, with the id in
212
+ `this.context.clientId`. The Electron templates have a preload script and a `window.ipc` whose `invoke()` returns the
213
+ payload or throws; code that reads the raw responses of an IPC layer of its own changes.
214
+
215
+ ### The components
216
+
217
+ See [Database](Database.md), [DockerIntegration](DockerIntegration.md) and [Logger](Logger.md).
218
+
219
+ - **Database**: a `null` or `undefined` value in a where condition is still left out (`invalidWhereValuesBehavior`),
220
+ so `findOneBy({id: undefined})` returns the first row: check the values taken from requests. With table or column
221
+ names outside ASCII, TypeORM 1 names indices and constraints differently: `namingStrategy: new NamingStrategyV03()`
222
+ (from `lakutata/orm`) keeps the names of TypeORM 0.3.
223
+ - **Docker**: `exec()` resolves `{exitCode, stdout, stderr}` (2.x: the output as a string); a string `cmd` of `exec()`
224
+ and `createTTY()` runs through `/bin/sh -c`, and `createTTY()` runs `/bin/sh` by default (2.x: `bash`).
225
+ - **Logger**: colors only when the standard output is a terminal and `NO_COLOR` is not set (2.x: always); the
226
+ `Authorization`, `Cookie`, `Set-Cookie` and `Proxy-Authorization` headers of a logged request are redacted
227
+ (`redactedHeaders: []` keeps them); a `level` that is not a level fails the launch.
228
+
229
+ ```typescript
230
+ import {Application, type ApplicationExitReason, type ApplicationOptions, type ILogger} from 'lakutata'
231
+ import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
232
+ import {GET} from 'lakutata/decorator/ctrl'
233
+ import {expressHTTPEntrypoint, jsonEnvelope} from 'lakutata/adapter/express'
234
+ import {delay, md5} from 'lakutata/utils'
235
+
236
+ class HashController extends Controller {
237
+ //2.x: Delay() and MD5() from 'lakutata/helper'
238
+ @GET('/hash')
239
+ public async hash(): Promise<{ hash: string }> {
240
+ await delay(10)
241
+ return {hash: md5('text').toString('hex')}
242
+ }
243
+ }
244
+
245
+ Application
246
+ .run((): ApplicationOptions => ({
247
+ id: 'com.example.app',
248
+ name: 'Example',
249
+ //Used only when the environment sets neither MODE nor NODE_ENV
250
+ mode: 'production',
251
+ components: {
252
+ entrypoint: buildEntrypoints({
253
+ controllers: [HashController],
254
+ http: expressHTTPEntrypoint({
255
+ port: 8080,
256
+ //The errors of the actions answered with 200 in {errno, errMsg, data, timestamp}
257
+ respond: jsonEnvelope({
258
+ actionErrorStatus: 200,
259
+ extra: (): Record<string, unknown> => ({timestamp: Date.now()})
260
+ })
261
+ })
262
+ })
263
+ },
264
+ bootstrap: ['entrypoint']
265
+ }))
266
+ .onDone((app: Application, log: ILogger, reason: ApplicationExitReason): void => {
267
+ log.info('%s stopped with code %d', app.appName, reason.code)
268
+ })
269
+ ```
270
+
271
+ ## 5. Tools that help
272
+
273
+ - `lakutata create --name probe --template <template> --path <a scratch directory>` makes a fresh 3.x project of the
274
+ same kind, to compare its `package.json`, `tsconfig.json` and scripts, and for Electron its preload script. `--init`
275
+ creates it in an existing directory, keeps the files already there and lists the ones it did not copy.
276
+ - `lakutata migration generate|create|run|revert|show` runs the migrations (the `MIGRATION_*` environment variables of
277
+ 2.x still work); `lakutata package bundle|exe|installer` packs the application. See [Command](Command.md).
278
+
279
+ ## 6. Checking the upgrade
280
+
281
+ ```bash
282
+ node --version # ^22.13.0 || >=24.11.0
283
+ npm ls lakutata @lakutata/core # one version of each
284
+ npm run build # tsc without errors: no 2.x import left
285
+ grep -rnE "process\.on\('SIG|\|\| ''|(startup|launched|beforeDestroy|acceptsOption)\(|AccessDenyException|=== 500" src
286
+ npm test
287
+ MODE=production npm start # the launch log, then Ctrl+C: onDone runs, exit code 0
288
+ ```
289
+
290
+ - The `grep` lists what compiles but behaves differently: signal handlers, empty strings given to options, members
291
+ named like the new hooks, comparisons with the error name or the status of 2.x. Check each line it finds.
292
+ - The launch shows the expected mode, no `InvalidObjectOptionsException`, and no warning that ends with
293
+ `is not a configurable property, it is ignored`.
294
+ - An invalid input is answered with 400 `E_INVALID_VALUE`, a denied access with 403 `E_ACCESS_DENY`, an unknown route
295
+ with 404, in the envelope that the clients expect.
296
+ - A broken configuration makes the process exit with code 1.
297
+ - `lakutata migration generate` against a copy of the production database generates no change: a change there means a
298
+ difference of naming or of entities (see `NamingStrategyV03` in section 4), not a migration to run.
299
+
300
+ ## 7. Common mistakes
301
+
302
+ - A search-and-replace of a renamed function also changes string literals, log messages, object keys and longer names
303
+ that contain it (`Delay` in `RequestDelay`): match whole words and read the diff. The new camelCase name can also
304
+ collide with a local variable or a parameter (`delay`, `paginator`, `md5`).
305
+ - One process must load one copy of the framework. `lakutata` ships ES module and CommonJS builds as separate files: an
306
+ ES module application that loads a CommonJS-only library requiring `lakutata` gets two copies, whose classes and
307
+ metadata differ. Keep the libraries built for both module systems, or the whole process on one of them.
308
+ - `process.env.X || ''` for a required option fails the launch; `|| undefined` lets the default apply.
309
+ - A member named `startup`, `launched`, `beforeDestroy` or `acceptsOption` is now called by the framework.
310
+ - The `NODE_ENV` of the deployment, not the `mode` option, decides the mode.
311
+ - The SQLite files of 2.x open with `type: 'better-sqlite3'`. `better-sqlite3` is a native addon built for one version
312
+ of Node.js: an Electron application needs the build made for its Electron.
313
+ - The hashes that 2.x made of passwords longer than 72 bytes no longer validate: plan a password reset for those users.
314
+ - Code that checks `statusCode === 500` for a denied access, or compares `err === 'AccessDenyException'`, no longer
315
+ matches: compare the `errno`.
@@ -0,0 +1,189 @@
1
+ # Lakutata Workspaces
2
+
3
+ A project made of several packages (an application, its web interface, shared libraries, native addons) is a
4
+ workspace: one repository whose root `package.json` lists the directories of its packages. The package manager (npm,
5
+ pnpm or yarn) links the packages to each other; the `lakutata` command runs their scripts in the order of their
6
+ dependencies, gives them one version, and adds new packages. It covers the part of lerna that these projects use.
7
+ Every template but `default` makes a workspace ([ProjectStructure](ProjectStructure.md), section 2).
8
+
9
+ ## 1. A workspace
10
+
11
+ The root `package.json` of a project made by `lakutata create --name shop --template web-react` (its migration and
12
+ Docker scripts left out):
13
+
14
+ ```json
15
+ {
16
+ "name": "shop-workspace",
17
+ "version": "0.0.1",
18
+ "private": true,
19
+ "workspaces": ["packages/*"],
20
+ "scripts": {
21
+ "build": "lakutata run rebuild",
22
+ "test": "npm run build && lakutata run test",
23
+ "dev": "lakutata run dev --parallel",
24
+ "start": "npm start -w app",
25
+ "version:patch": "lakutata version patch --yes"
26
+ },
27
+ "devDependencies": {
28
+ "@lakutata/cli": "^3.0.0",
29
+ "@types/node": "^22.0.0",
30
+ "typescript": "^6.0.3"
31
+ }
32
+ }
33
+ ```
34
+
35
+ - **The packages** are the directories that the patterns of `workspaces` match and that hold a `package.json` with a
36
+ `name`: `*` matches within one directory name, `**` any number of directories, and `!packages/old` leaves a directory
37
+ out. `node_modules` and the directories starting with a dot are never searched. A pnpm workspace lists its patterns in
38
+ `pnpm-workspace.yaml` instead, as a list under `packages:` (`- 'packages/*'` lines, or `['packages/*']`).
39
+ - **The commands work from any directory** of the workspace: they use the nearest parent directory that declares a
40
+ workspace.
41
+ - **The dependencies between packages** are the ones in `dependencies`, `devDependencies`, `peerDependencies` or
42
+ `optionalDependencies`, whatever their range (`^0.0.1`, `*`, `workspace:*`, `file:`).
43
+ - **The build tools** (`@lakutata/cli`, `typescript`, `@types/node`) are development dependencies of the root, so every
44
+ package can use them in its scripts.
45
+
46
+ `lakutata list` shows the packages, their versions, their directories and the packages each one depends on. Here,
47
+ `app` (the server) depends on `ui` (the web interface), whose built pages it serves:
48
+
49
+ ```
50
+ 2 packages, version 0.0.1
51
+ app 0.0.1 (private) packages/app → ui
52
+ ui 0.0.1 (private) packages/ui
53
+ ```
54
+
55
+ ## 2. Running scripts
56
+
57
+ `lakutata run <script>` runs an npm script in every package that has it. A package starts once the packages it depends
58
+ on (directly or not) have finished, and packages that do not depend on each other run at the same time. Each line of
59
+ output starts with the name of its package:
60
+
61
+ ```
62
+ lib │ start lib
63
+ tools │ start tools
64
+ tools │ end tools
65
+ lib │ end lib
66
+ app │ start app
67
+ app │ end app
68
+ ✔ build ran in tools, lib, app
69
+ ```
70
+
71
+ | Option | Meaning |
72
+ |---|---|
73
+ | `--parallel` | Start all the packages at once, without waiting for their dependencies: for scripts that keep running, such as dev servers and watchers (`lakutata run dev --parallel`) |
74
+ | `--scope <names...>` | Only these packages; `*` matches any characters (`--scope 'app' '@shop/*'`). The packages they depend on are neither run nor waited for, and a scope matching no package is an error |
75
+ | `--concurrency <n>` | How many packages run at a time (the number of processors by default) |
76
+ | `--no-bail` | Go on after a failure: the packages that depend on the failed one are skipped, the others still run |
77
+
78
+ - **Failures**: by default, no package starts after a failure, and those already running finish. The command ends with
79
+ `error: build failed in lib, not run in app` and the exit code 1.
80
+ - **No package has the script**: the command prints `No package has the script lint` and succeeds.
81
+ - **The package manager**: the scripts run with `npm run`, `pnpm run` or `yarn run`, chosen from the `packageManager`
82
+ field of the root `package.json` (`"pnpm@9.15.0"`), else from the files at the root (`pnpm-lock.yaml` or
83
+ `pnpm-workspace.yaml`, `yarn.lock`, `package-lock.json`); npm by default.
84
+ - **Arguments**: the scripts take no extra arguments (`lakutata run build -- --watch` is refused): add a script
85
+ instead.
86
+
87
+ ## 3. Giving the packages a version
88
+
89
+ All the packages of a workspace share one version. `lakutata version <bump>` gives it to all of them, and updates the
90
+ dependencies between them. From version 1.2.3:
91
+
92
+ ```bash
93
+ lakutata version patch # 1.2.4, after a confirmation in a terminal
94
+ lakutata version minor --git --yes # 1.3.0, committed and tagged v1.3.0
95
+ lakutata version preminor --preid beta # 1.3.0-beta.0, then prerelease gives 1.3.0-beta.1
96
+ lakutata version 2.0.0
97
+ ```
98
+
99
+ The output, in a workspace at 0.0.16:
100
+
101
+ ```
102
+ Version 0.0.16 → 0.0.17
103
+ app 0.0.16 → 0.0.17
104
+ lib 0.0.16 → 0.0.17
105
+ tools 0.0.16 → 0.0.17
106
+ ✔ Version 0.0.17 given (packages/app/package.json, packages/lib/package.json, packages/tools/package.json, package.json)
107
+ ```
108
+
109
+ - **The current version** is the `version` of `lerna.json` when that file gives one, else the one of the root
110
+ `package.json`, else the highest version among the packages, a prerelease included (`0.0.0` when there is none). The
111
+ new version is written back there, and into every package, the private ones included.
112
+ - **The bumps** are `major`, `minor`, `patch`, `premajor`, `preminor`, `prepatch`, `prerelease`, or an explicit version.
113
+ `--preid <id>` names the prerelease of the `pre…` bumps (`beta` gives `1.3.0-beta.0`; without it, `1.3.0-0`).
114
+ - **The dependencies between the packages** follow: `^0.0.16` becomes `^0.0.17`, and so do `~`, `>=`, `=` and
115
+ `workspace:^` ranges. `*`, `workspace:*` and `file:` dependencies are left as they are. With `0.x` versions this
116
+ matters, because `^0.0.16` does not accept `0.0.17`.
117
+ - **The lock file** follows too: `package-lock.json` or `pnpm-lock.yaml` is updated, so that `npm ci` (or
118
+ `pnpm install --frozen-lockfile`) still accepts it. A `yarn.lock` is not: the command reminds you to run
119
+ `yarn install`.
120
+ - **`--git`** commits the changed files, and only them, with the message `v1.3.0` and tags the commit `v1.3.0`. Nothing
121
+ is pushed. The repository and the tag are checked before a file is changed.
122
+ - **The confirmation** is asked only in a terminal: in CI, or when the output is piped, the version is given at once.
123
+ `--yes` skips it in a terminal too.
124
+
125
+ ## 4. Adding a package
126
+
127
+ `lakutata add <name>` creates a TypeScript package in the workspace; `--to <package>` adds it to the dependencies of
128
+ another package. In the `web-react` project of section 1, a package of code shared by the server and the web interface:
129
+
130
+ ```bash
131
+ lakutata add @shop/shared --to app
132
+ ```
133
+
134
+ ```
135
+ ✔ Package @shop/shared 0.0.1 added: packages/shared (package.json, tsconfig.json, src/index.ts, src/tests/index.spec.ts, README.md), a dependency of app
136
+ Next: npm install (links the package), then npm run rebuild -w @shop/shared
137
+ ```
138
+
139
+ `--to` names one package: add the dependency to `ui` by hand, in `packages/ui/package.json`:
140
+
141
+ ```json
142
+ {
143
+ "dependencies": {
144
+ "@shop/shared": "^0.0.1"
145
+ }
146
+ }
147
+ ```
148
+
149
+ Then `npm install` links the new package, and `lakutata list` shows it:
150
+
151
+ ```
152
+ 3 packages, version 0.0.1
153
+ app 0.0.1 (private) packages/app → ui, @shop/shared
154
+ @shop/shared 0.0.1 (private) packages/shared
155
+ ui 0.0.1 (private) packages/ui → @shop/shared
156
+ ```
157
+
158
+ `npm run build` (`lakutata run rebuild`) now builds `@shop/shared` first, then `ui`, then `app`, and both import it as
159
+ any package: `import {greet} from '@shop/shared'`.
160
+
161
+ - **Where**: in the directory of the packages of the workspace, without the scope of the name: the directory of the
162
+ first pattern such as `apps/*` (`packages/*` when there is one). A workspace whose patterns have no such directory
163
+ (only packages named one by one, or `packages/**`) is refused.
164
+ - **Its version** is the version of the workspace (`0.0.1` without one, or with the independent versions of lerna), and
165
+ `--to` adds the dependency `"@shop/shared": "^0.0.1"`.
166
+ - **Its files**: `src/index.ts` (an example function to replace), a test run by `node --test`, and a `package.json`
167
+ whose scripts are `clean`, `build` (`tsc`), `rebuild` and `test`. It compiles `src` into `build`, with its type
168
+ declarations, which its `exports` give to the other packages. Code that the web interface also uses must not use the
169
+ modules of Node.js.
170
+ - **Private**: the package is `"private": true`. Remove that line to publish it (`npm publish -w @shop/shared`).
171
+ - **Nothing is installed**: run the install of the package manager of the workspace afterwards, as the command says
172
+ (`pnpm install`, `yarn install`), so that the other packages see the new one.
173
+
174
+ `lakutata add <name> --native gyp|cmake|rust` creates a native addon instead: see [NativeAddons](NativeAddons.md).
175
+
176
+ ## 5. Coming from lerna
177
+
178
+ | lerna | lakutata |
179
+ |---|---|
180
+ | `lerna run rebuild` | `lakutata run rebuild` |
181
+ | `lerna run dev --parallel` | `lakutata run dev --parallel` |
182
+ | `lerna version patch --no-git-tag-version --no-push -y` | `lakutata version patch --yes` |
183
+ | `lerna version patch` | `lakutata version patch --git` (the tag is not pushed) |
184
+
185
+ - A workspace can keep its `lerna.json`: lakutata reads and writes its `version` only. The packages come from the
186
+ `workspaces` of the root `package.json` (or `pnpm-workspace.yaml`), not from the `packages` of `lerna.json`.
187
+ - lakutata gives all the packages one version: a `lerna.json` with `"version": "independent"` cannot be bumped.
188
+ - It does not write changelogs, cache builds, select the packages changed since a release, or publish: publish with
189
+ `npm publish -w <package>`. Workspaces that need these keep lerna (or nx, turborepo) alongside it.