lakutata 3.1.2 → 3.1.4

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.
package/README.md CHANGED
@@ -144,8 +144,9 @@ on. Each package's README lists its exceptions.
144
144
  `Entrypoint.md`, `Middlewares.md`, `HTTP.md`, `CLI.md`, `ServiceCalls.md`, `GRPC.md`, `NATS.md`, `Schedules.md`,
145
145
  `Queue.md`, `Database.md`, `DatabaseDrivers.md`, `MongoEmbedded.md`, `Entities.md`, `Migrations.md`, `Cache.md`,
146
146
  `Mutex.md`, `I18n.md`, `DockerIntegration.md`, `ContainerdIntegration.md`, `Monitor.md`, `Security.md`, `DBus.md`,
147
- `MCP.md`, `WebRTC.md`, `FFI.md`, `Command.md`, `Workspaces.md`, `NativeAddons.md`, `Packaging.md`, `Installers.md`;
148
- and the examples, in `doc/en/examples` and `doc/zh/examples`: whole applications built from the templates.
147
+ `MCP.md`, `WebRTC.md`, `FFI.md`, `Math.md`, `Netif.md`, `Command.md`, `Workspaces.md`, `NativeAddons.md`,
148
+ `Packaging.md`, `Installers.md`; and the examples, in `doc/en/examples` and `doc/zh/examples`: whole applications
149
+ built from the templates.
149
150
  - `@lakutata/cli`: creating, building and packaging applications.
150
151
  - The official Agent Skill for AI coding assistants that work on lakutata projects:
151
152
  [lakutata.com/skill](https://lakutata.com/skill/).
package/README.zh-CN.md CHANGED
@@ -121,7 +121,7 @@ Application
121
121
  ## 另见
122
122
 
123
123
  - 网站 <https://lakutata.com/zh/>:指南、示例和 API 参考,中英文都有。
124
- - 指南在这个包的 `doc/zh`(中文)和 `doc/en`(英文)中:`Overview.md`、`GettingStarted.md`、`ProjectStructure.md`、`UpgradingFrom2.md`、`UpgradingFrom1.md`、`Application.md`、`Objects.md`、`Injection.md`、`DTOValidation.md`、`Exceptions.md`、`Logger.md`、`Decorator.md`、`Time.md`、`HelperFunctions.md`、`Controllers.md`、`Entrypoint.md`、`Middlewares.md`、`HTTP.md`、`CLI.md`、`ServiceCalls.md`、`GRPC.md`、`NATS.md`、`Schedules.md`、`Queue.md`、`Database.md`、`DatabaseDrivers.md`、`MongoEmbedded.md`、`Entities.md`、`Migrations.md`、`Cache.md`、`Mutex.md`、`I18n.md`、`DockerIntegration.md`、`ContainerdIntegration.md`、`Monitor.md`、`Security.md`、`DBus.md`、`MCP.md`、`WebRTC.md`、`FFI.md`、`Command.md`、`Workspaces.md`、`NativeAddons.md`、`Packaging.md`、`Installers.md`;示例在 `doc/zh/examples` 和 `doc/en/examples` 中:从模板构建的完整应用。
124
+ - 指南在这个包的 `doc/zh`(中文)和 `doc/en`(英文)中:`Overview.md`、`GettingStarted.md`、`ProjectStructure.md`、`UpgradingFrom2.md`、`UpgradingFrom1.md`、`Application.md`、`Objects.md`、`Injection.md`、`DTOValidation.md`、`Exceptions.md`、`Logger.md`、`Decorator.md`、`Time.md`、`HelperFunctions.md`、`Controllers.md`、`Entrypoint.md`、`Middlewares.md`、`HTTP.md`、`CLI.md`、`ServiceCalls.md`、`GRPC.md`、`NATS.md`、`Schedules.md`、`Queue.md`、`Database.md`、`DatabaseDrivers.md`、`MongoEmbedded.md`、`Entities.md`、`Migrations.md`、`Cache.md`、`Mutex.md`、`I18n.md`、`DockerIntegration.md`、`ContainerdIntegration.md`、`Monitor.md`、`Security.md`、`DBus.md`、`MCP.md`、`WebRTC.md`、`FFI.md`、`Math.md`、`Netif.md`、`Command.md`、`Workspaces.md`、`NativeAddons.md`、`Packaging.md`、`Installers.md`;示例在 `doc/zh/examples` 和 `doc/en/examples` 中:从模板构建的完整应用。
125
125
  - `@lakutata/cli`:创建、构建和打包应用。
126
126
  - 官方 Agent Skill(智能体技能),供 AI 编程助手在 lakutata 项目中使用:<https://lakutata.com/zh/skill/>。
127
127
 
package/doc/en/GRPC.md CHANGED
@@ -56,10 +56,15 @@ The options of `grpcServiceEntrypoint`:
56
56
  | `tls` | None (plain HTTP/2) | `{cert, key}` for TLS; with `ca` too, the clients must present a certificate signed by it (mutual TLS) |
57
57
  | `maxMessageSize` | 4 MiB | The largest request, before and after decompression; a larger one fails with `RESOURCE_EXHAUSTED` |
58
58
  | `compression` | `'identity'` | `'gzip'` or `'deflate'` to compress the responses for the clients that accept it. Compressed requests are always accepted |
59
- | `listening` | | `(address) => void`, called once the server listens; the start waits for it |
59
+ | `listening` | | `(address, listeners) => void`, called once the server listens; the start waits for it |
60
+ | `interface`, `family`, `strict`, `onError` | | Listen on a network interface instead of an address, as the HTTP adapters do (see [HTTP](HTTP.md), section 2); not with `host` or `socketPath` |
60
61
  | `middlewares` | | The middlewares of the actions called over gRPC, run after the ones of the entrypoint |
61
62
  | `name` | | The name of the adapter, to tell several apart |
62
63
 
64
+ With `interface`, the package `@lakutata/netif` has to be installed. The `address` given to `listening` is then the
65
+ first one listened on, or `null` while the interface does not exist or has no address yet, and `listeners` are the
66
+ listeners of the interface.
67
+
63
68
  The server has no reflection service and no health service.
64
69
 
65
70
  ## 2. Calling them from another application
@@ -248,4 +253,5 @@ seconds by default) to complete. Then the event streams end and the server close
248
253
 
249
254
  The package also has the layers the service is built on, usable without lakutata: `GRPCConnection`, a client for any
250
255
  gRPC server (unary, server streaming and bidirectional calls, metadata, TLS); `GRPCServer`; and `encode`/`decode`, a
251
- protobuf codec driven by schemas. The README of `@lakutata/grpc` describes them.
256
+ protobuf codec driven by schemas. The README of `@lakutata/grpc` describes them. `GRPCServer.listenWith()` listens
257
+ through the listeners of network interfaces (`listenOnAll()` of `@lakutata/netif`), as the adapter does.
package/doc/en/HTTP.md CHANGED
@@ -123,6 +123,8 @@ The options of `expressHTTPEntrypoint()`:
123
123
  | `fallback` | | Answers the requests outside the paths of the actions, such as the pages of a web UI (section 7) |
124
124
  | `stop` | | Called when the adapter stops, once its server is closed |
125
125
  | `heartbeat` | `15000` | How often a comment keeps the event streams alive, in milliseconds (section 5) |
126
+ | `listening` | | `(server, listeners) => …`: called once the server listens, or once the listeners of the interface are made (see below) |
127
+ | `interface` | | The network interface to listen on instead of an address, with `family`, `strict` and `onError` (see below) |
126
128
 
127
129
  `fastifyHTTPEntrypoint()` takes the same options, except `server` and `app`, replaced by `fastify`: a Fastify instance,
128
130
  or the options of the instance to create (`bodyLimit`, 1 MiB by default; `trustProxy`; `connectionTimeout`,
@@ -146,6 +148,38 @@ the event streams; Fastify also answers 503 to the requests that still arrive on
146
148
  progress get the `shutdownTimeout` of the entrypoint (after it, they are answered with 503), then up to one second to
147
149
  send their responses, before the remaining connections are closed, the WebSockets included.
148
150
 
151
+ ### On a network interface
152
+
153
+ A device with several network interfaces often serves its pages on one of them only, and the address of that interface
154
+ comes from DHCP and changes. With `interface`, the adapter listens on the interface instead of an address, whatever its
155
+ addresses, and goes on listening when they change and when the interface is unplugged and plugged back. By default it
156
+ uses the package `@lakutata/netif`, installed apart, which compiles a native addon (see [Netif](Netif.md)).
157
+
158
+ ```typescript
159
+ import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
160
+
161
+ //The pages on eth1 only, whatever its address
162
+ export const http = expressHTTPEntrypoint({
163
+ port: 8080,
164
+ interface: 'eth1',
165
+ listening: (_server, listeners): void => console.log(listeners?.map(listener => listener.addresses))
166
+ })
167
+ ```
168
+
169
+ | Option | Default | Meaning |
170
+ |---|---|---|
171
+ | `interface` | | The name of the interface (`'eth1'`; on Windows the name of the network connection), `{mac: '52:54:00:12:34:56'}` or `{index: 3}`; several in an array, and then `port` cannot be `0`. It cannot be given with `host` |
172
+ | `family` | `'both'` | `'IPv4'`, `'IPv6'` or both |
173
+ | `strict` | `false` | Fails the start where the system cannot refuse the connections that reach the addresses of the interface through another interface, and stops listening while it cannot (see [Netif](Netif.md), section 2) |
174
+ | `onError` | the log of the application | Receives the errors of listening again while the application runs, such as a port taken by another program; the adapter retries by itself |
175
+ | `interfaceListening` | `@lakutata/netif` | Another implementation of the listening on interfaces: an `IInterfaceListening`, the contract of lakutata that `@lakutata/netif` implements |
176
+
177
+ - The interface may not exist yet when the application starts (a USB network adapter not plugged in): the start
178
+ succeeds, and the adapter listens once the interface appears. Until then the `addresses` of the listeners are empty.
179
+ - The server itself does not listen, and its `address()` is `null`: the listeners give the port and the addresses.
180
+ - `family`, `strict`, `onError` and `interfaceListening` without `interface` fail the creation of the adapter, as do
181
+ `interface` and `host` together.
182
+
149
183
  ## 3. Routes
150
184
 
151
185
  | Decorator | Methods |
package/doc/en/MCP.md CHANGED
@@ -248,7 +248,8 @@ Application.run(() => ({
248
248
  |---|---|---|
249
249
  | `port`, `host`, `path` | `3000`, `'127.0.0.1'`, `'/mcp'` | Where it listens |
250
250
  | `authenticate` | | `(request) => MCPAuthInfo \| undefined`: the authentication of the client; throwing answers 401, returning `undefined` serves the request without one |
251
- | `allowedHosts`, `allowedOrigins` | the local names, on a local address | The `Host` and `Origin` headers accepted (403 otherwise); `false` checks none |
251
+ | `allowedHosts`, `allowedOrigins` | the local names, on a local address or the loopback interface | The `Host` and `Origin` headers accepted (403 otherwise); `false` checks none |
252
+ | `interface`, `family`, `strict`, `onError` | | Listen on a network interface instead of `host`, with the package `@lakutata/netif` (see [HTTP](HTTP.md), section 2) |
252
253
  | `legacy` | `'stateless'` | The clients of the revisions of 2025: served without sessions, or refused with `'reject'` |
253
254
  | `maxRequestBodySize` | 4 MiB | A larger body is answered with 413 |
254
255
  | `bus` | in memory | Where the notifications go, to share them between the instances of the application |
@@ -258,7 +259,8 @@ Application.run(() => ({
258
259
  - It serves the protocol over Streamable HTTP: the revision 2026-07-28, and the revisions of 2025 without sessions.
259
260
  - On a local address, the `Host` and `Origin` headers are checked against the local names, which protects the server
260
261
  from DNS rebinding: behind a proxy, list the names the clients use. On another address, nothing is checked unless you
261
- list names.
262
+ list names. The same goes for `interface`: the loopback interface is checked against the local names, and on another
263
+ interface the clients of the network reach the server, so set `allowedHosts` and `authenticate`.
262
264
  - When the application stops, the calls in progress are answered, then the port closes.
263
265
 
264
266
  **Sharing the port of the HTTP adapter.** With `listen: false`, mount the `handler` of the adapter on the Express
package/doc/en/Math.md ADDED
@@ -0,0 +1,394 @@
1
+ # Lakutata Math
2
+
3
+ The package `@lakutata/math` does mathematics in Node.js, both symbolic and numeric: algebra and calculus on formulas,
4
+ equations and inequalities, decimals of any precision, matrices, statistics and probability distributions, numeric
5
+ solutions, units, number theory. It is not part of `lakutata`, and it needs no application: its functions are imported
6
+ and called anywhere. What it is like:
7
+
8
+ - **Formulas are written as on paper**: `x^2 - 5x + 6`, `2(x + 1)`, `5!`, `sin(pi/6)`. A result is written the same
9
+ way, so it can be read back, and in TeX for display.
10
+ - **Results are exact**: `0.1 + 0.2` is `3/10`, `sqrt(8)` is `2sqrt(2)`, the roots of `x^2 - 2` are `-sqrt(2)` and
11
+ `sqrt(2)`. A number is computed only when one is asked for: a double, or as many correct decimal digits as wanted.
12
+ - **Symbolic and numeric in one package**: when an operation has no exact answer (an integral, the roots of a polynomial
13
+ of degree five), the numeric methods take the same formula.
14
+ - **A native addon** does the work, built on SymEngine, Boost, Eigen and mpdecimal: it is compiled code, so it is fast.
15
+ The long operations run in its own threads, with a time limit and a cancellation, so the event loop stays free.
16
+
17
+ The README of `@lakutata/math` describes every function and its options; this guide goes through what the package
18
+ computes, with an example of each kind.
19
+
20
+ ```bash
21
+ npm install @lakutata/math
22
+ ```
23
+
24
+ The addon is compiled when the package is installed, so the machine needs Python 3 and a C++17 compiler (the README
25
+ lists them for each system); it takes from about half a minute to a few minutes. The package manager has to run the
26
+ install script of the package: npm runs it, and may warn that the script is "not yet covered by allowScripts"
27
+ (`npm install-scripts approve @lakutata/math` records that you allow it); Bun and pnpm run it only for the packages they
28
+ are told to trust (`trustedDependencies` in the package.json for Bun, `pnpm approve-builds` for pnpm). Without the
29
+ addon, loading the package throws an error that tells how to build it.
30
+
31
+ ## 1. Formulas and results
32
+
33
+ `expression()` reads a formula. A number next to a name or a parenthesis multiplies it (`2x`, `3(x + 1)`), `^` is a
34
+ power, and the constants (`pi`, `e`, `i`, `inf`) and the functions (`sin`, `ln`, `sqrt`, `gamma`, `erf`, `zeta`…) have
35
+ their usual names; the README lists them all. An expression does not change: each operation gives a new one.
36
+
37
+ ```typescript
38
+ import {createMath, expression} from '@lakutata/math'
39
+
40
+ console.log(expression('0.1 + 0.2').toString()) //3/10
41
+ console.log(expression('sqrt(8) + sin(pi/6)').toString()) //2sqrt(2) + 1/2
42
+ console.log(expression('3(x + 1)^2 - x').expand().toString()) //3x^2 + 5x + 3
43
+ console.log(expression('e^(-x^2/2)/sqrt(2pi)').toTeX()) //\frac{e^{-\frac{x^{2}}{2}} \sqrt{2}}{2 \sqrt{\pi}}
44
+ console.log(expression('cos(10^30)').toNumber(), Math.cos(1e30)) //-0.9959311944053957 -0.9999564608959665
45
+ console.log(expression('pi').toDecimal(50)) //3.1415926535897932384626433832795028841971693993751
46
+ const finance = createMath({definitions: {'rate': '0.13', 'net(x)': 'x/(1 + rate)'}})
47
+ console.log(finance.expression('net(113)').toString()) //100
48
+ console.log(finance.expression('p - net(p)').diff('p').toString()) //13/113
49
+ ```
50
+
51
+ - `toString()` gives a formula that `expression()` reads back, `toTeX()` its TeX (for KaTeX or MathJax).
52
+ - `toNumber()` gives the nearest double. It is computed from the decimals of the exact value, so all its digits are
53
+ right, even where doubles have none: as a double, `1e30` is not exactly 10^30, and `Math.cos(1e30)` is the cosine of
54
+ another number. `toDecimal(digits)` gives that many correct significant digits, as a string.
55
+ - `substitute({x: 2})` puts values in for the variables; the result stays exact.
56
+ - `createMath({definitions})` makes a scope of functions and constants written as formulas, with the same functions
57
+ as the package. A formula sees through its definitions: derivatives and equations use them. The functions the package
58
+ exports belong to `math`, a scope with no definitions.
59
+
60
+ ## 2. Algebra and calculus
61
+
62
+ ```typescript
63
+ import {expression} from '@lakutata/math'
64
+
65
+ console.log(expression('x^6 - y^6').factor().toString()) //(x^2 - x*y + y^2)(x + y)(x - y)(x^2 + x*y + y^2)
66
+ console.log(expression('(x^2 - 1)/(x^2 + 2x + 1)').cancel().toString()) //(x - 1)/(x + 1)
67
+ console.log(expression('sin(x)^4 - cos(x)^4').simplify().toString()) //-cos(2x)
68
+ console.log(expression('x^2*sin(x)').diff('x').toString()) //2x*sin(x) + x^2*cos(x)
69
+ console.log(expression('x*e^x').integrate('x').toString()) //e^x*(x - 1)
70
+ console.log(expression('1/(x^2 + 1)').integrate('x', '-inf', 'inf').toString()) //pi
71
+ console.log(expression('(1 + a/x)^x').limit('x', 'inf').toString()) //e^a
72
+ console.log(expression('e^x').series('x', 0, 4).toString()) //x^3/6 + x^2/2 + x + 1
73
+ console.log(expression('sum(k^2, k, 1, n)').toString()) //n^3/3 + n^2/2 + n/6
74
+ console.log(expression('t^2*e^(-3t)').laplace('t', 's').toString()) //2/(s + 3)^3
75
+ ```
76
+
77
+ - **Polynomials and fractions**: `expand`, `factor` (over the rationals, in any number of variables), `cancel`,
78
+ `together`, `apart` (partial fractions) and `completeSquare`.
79
+ - **Simplification**: `simplify()` looks for a shorter form, by identities that hold for every value of the variables
80
+ (trigonometric, hyperbolic, exponential, nested roots); nothing that depends on the signs of the variables, so
81
+ `sqrt(x^2)` stays as it is.
82
+ - **Integrals**: by partial fractions, a table, substitution, integration by parts, and the special functions of
83
+ integrals (`sin(x)/x` gives `Si(x)`). Each antiderivative is checked by its derivative before it is returned. A
84
+ definite integral is split at the poles inside its interval; it is `inf` where it grows without bound.
85
+ - **Limits** by Gruntz's algorithm, from both sides or from one, at a point or at infinity, with the indeterminate
86
+ forms (`0/0`, `1^inf`, `inf - inf`…).
87
+ - **Series** (`series(variable, point, order)`), **sums and products** in closed form (`sum(1/k^2, k, 1, inf)` is
88
+ `pi^2/6`), **piecewise** expressions (`piecewise(x^2, x < 0, x)`), and the **Laplace transform** and its inverse.
89
+
90
+ Most of these operations have an asynchronous version (section 11).
91
+
92
+ ## 3. Equations and inequalities
93
+
94
+ ```typescript
95
+ import {equation, type Inequality, parse, solve} from '@lakutata/math'
96
+
97
+ console.log(equation('x^3 - 6x^2 + 11x - 6 = 0').solve('x').map(String)) //['1', '2', '3']
98
+ console.log(equation('x^4 = 16').solve('x').map(String)) //['-2', '2', '-2i', '2i']
99
+ console.log(equation('x^4 = 16').solve('x', {domain: 'real'}).map(String)) //['-2', '2']
100
+ console.log(equation('x^2 - (a + b)x + a*b = 0').solve('x').map(String)) //['a', 'b']
101
+ const [solution] = solve(['x + y = 3', 'x - y = 1'], ['x', 'y'])
102
+ console.log(solution.x.toString(), solution.y.toString()) //2 1
103
+ console.log((parse('(x + 1)/(x - 2) >= 0') as Inequality).solve('x').set.toString()) //(-inf, -1] ∪ (2, inf)
104
+ ```
105
+
106
+ - A polynomial equation is factored first, so its roots come out simple (1, 2, 3, not the radicals of Cardano's
107
+ formula); the factors are solved exactly up to degree four, and `a x^n + b` of any degree. The solutions are complex
108
+ numbers unless `{domain: 'real'}` is given.
109
+ - `solve(equations, variables)` solves a system of linear equations, in terms of the free variables when it has
110
+ infinitely many solutions.
111
+ - An inequality of fractions of polynomials gives its real solutions as a set of intervals and values (`MathSet`), with
112
+ union, intersection, complement and membership.
113
+
114
+ ## 4. Decimals
115
+
116
+ `decimal()` gives a decimal number with a chosen precision (32 significant digits by default, up to 100000): for amounts
117
+ of money, or for values that need many digits. Each operation rounds its result correctly, halves to even.
118
+
119
+ ```typescript
120
+ import {decimal, expression} from '@lakutata/math'
121
+
122
+ console.log(decimal(0.1).add(0.2).toString()) //0.3
123
+ console.log(decimal('19.99').mul(3).toFixed(2)) //59.97
124
+ console.log(decimal(1, {precision: 50}).div(7).toString()) //0.14285714285714285714285714285714285714285714285714
125
+ console.log(decimal(2, {precision: 40}).sqrt().toString()) //1.41421356237309504880168872420969807857
126
+ console.log(expression('gamma(1/3)').toDecimal(40)) //2.678938534707747633655692940974677644129
127
+ console.log(expression('e^(pi*sqrt(163))').toDecimal(40)) //262537412640768743.9999999999992500725972
128
+ ```
129
+
130
+ - A number is read as it is written (`0.1` is one tenth, not the binary value of the double), a string as a decimal
131
+ number (`'19.99'`), a bigint exactly.
132
+ - Besides the arithmetic, decimals have roots, `exp` and the logarithms, the trigonometric and hyperbolic functions,
133
+ `gamma`, `erf`, `zeta`, `lambertw` and the rounding functions. `toDecimal()` of an expression computes the same
134
+ functions and more special ones (`polygamma`, `beta`, `Si`, `Ei`…).
135
+ - `toDecimal()` computes the value again with more digits until two results agree, so every digit it gives is right:
136
+ `e^(pi*sqrt(163))` is not an integer, though its first twelve decimals are nines.
137
+
138
+ ## 5. Matrices
139
+
140
+ There are two kinds of matrices: of numbers, for computing, and of expressions, for exact linear algebra.
141
+
142
+ ```typescript
143
+ import {expressionMatrix, linalg, matrix} from '@lakutata/math'
144
+
145
+ const a = matrix([[4, 1], [2, 3]])
146
+ console.log(linalg.solve(a, [5, 5])) //Float64Array(2) [1, 1]
147
+ console.log(linalg.inverse(a).toString(), linalg.determinant(a)) //[0.3, -0.1; -0.2, 0.4] 10
148
+ const m = expressionMatrix('[a, b; c, d]')
149
+ console.log(m.determinant().toString()) //a*d - b*c
150
+ console.log(m.inverse().toString()) //[d/(a*d - b*c), -b/(a*d - b*c); -c/(a*d - b*c), a/(a*d - b*c)]
151
+ console.log(expressionMatrix('[2, 1; 1, 2]').eigenvalues().map(({value, multiplicity}) => [value.toString(), multiplicity])) //[['1', 1], ['3', 1]]
152
+ ```
153
+
154
+ - `matrix()` holds doubles in a `Float64Array`, and `linalg` computes with Eigen: systems, least squares, inverses,
155
+ determinants, the decompositions (LU, QR, SVD, Cholesky, eigenvalues, Schur), the exponential and the square root of
156
+ a matrix, Sylvester and Lyapunov equations, and sparse matrices with their direct and iterative solvers.
157
+ - `expressionMatrix()` holds expressions: determinants, inverses, systems, reduced row echelon forms, null spaces,
158
+ characteristic polynomials, eigenvalues and eigenvectors, all exact, also with variables. `jacobian()` gives the
159
+ Jacobian matrix of expressions.
160
+
161
+ ## 6. Statistics and probability
162
+
163
+ ```typescript
164
+ import {stats} from '@lakutata/math'
165
+
166
+ const values = [2, 4, 4, 4, 5, 5, 7, 9]
167
+ console.log(stats.mean(values), stats.median(values), stats.standardDeviation(values)) //5 4.5 2
168
+ console.log(stats.quantile(values, [0.25, 0.75])) //Float64Array(2) [4, 5.5]
169
+ const iq = stats.normal({mean: 100, standardDeviation: 15})
170
+ console.log(iq.cdf(130) - iq.cdf(70)) //0.9544997361036416
171
+ console.log(iq.quantile(0.975)) //129.3994597681008
172
+ console.log(stats.binomial({trials: 10, probability: 0.3}).pdf(3)) //0.2668279319999998
173
+ console.log(stats.random(42).integers(5, 1, 7)) //Float64Array(5) [1, 1, 6, 6, 5]
174
+ ```
175
+
176
+ - The statistics of values: sums (compensated for their roundings), means, medians, modes, variances (of a population
177
+ by default, of a sample with `{normalization: 'unbiased'}`), quantiles, covariances, correlations, z-scores.
178
+ - 24 probability distributions, computed by Boost.Math, with their density, cdf, quantiles, moments and samples. Their
179
+ parameters are given by name (`{shape, scale}`), since libraries parameterize them differently; the README gives the
180
+ equivalents in SciPy.
181
+ - `stats.random(seed)` gives a generator (xoshiro256**): from the same seed, it draws the same uniform numbers and
182
+ integers, on every platform. It is not meant for cryptography.
183
+
184
+ ## 7. Numeric solutions
185
+
186
+ When an equation or an integral has no exact solution, or numbers are enough, `numeric` takes the same formulas:
187
+
188
+ ```typescript
189
+ import {numeric} from '@lakutata/math'
190
+
191
+ console.log(numeric.findRoot('cos(x) = x', 'x', {bracket: [0, 1]}).root) //0.7390851332151606
192
+ console.log(numeric.findRoot('x^5 - x - 1', 'x', {bracket: [1, 2]}).root) //1.167303978261426
193
+ const {solution, converged} = numeric.solveSystem(['x^2 + y^2 = 4', 'x*y = 1'], ['x', 'y'], [2, 0])
194
+ console.log(solution, converged) //Float64Array(2) [1.9318516525781366, 0.5176380902050416] true
195
+ console.log(numeric.integrate('e^(-x^2)*ln(1 + x)', 'x', [0, Infinity]).value) //0.36623762882311717
196
+ const decay = numeric.solveODE({derivatives: '-2y', states: 'y', span: [0, 1], initial: 1}, {points: [0.5, 1]})
197
+ console.log(decay.y.data) //Float64Array(2) [0.3678795037443847, 0.13533535227865426]
198
+ ```
199
+
200
+ - `polynomialRoots` gives all the roots of a polynomial, complex ones too; `findRoot` a root in a bracket or from a
201
+ guess; `solveSystem` a solution of nonlinear equations.
202
+ - `integrate` computes a definite integral by QUADPACK's method (as SciPy's `quad`), on infinite intervals too, and
203
+ with singularities at the ends or at given points.
204
+ - `solveODE` solves ordinary differential equations and systems of them, by an explicit Runge-Kutta method (as SciPy's
205
+ RK45) or, for stiff equations, by Radau IIA.
206
+ - When a method does not converge, its result says so (`converged`, `completed`) instead of throwing.
207
+
208
+ ## 8. Units and physical constants
209
+
210
+ ```typescript
211
+ import {constant, unit} from '@lakutata/math'
212
+
213
+ console.log(unit('5 cm + 2 inch').toString()) //10.08 cm
214
+ console.log(unit('100 km/h').to('m/s').toString()) //(250/9) m / s
215
+ console.log(unit('20 degC').to('degF').toString()) //68 degF
216
+ console.log(unit('1.5 h').split(['h', 'min']).map(String)) //['1 h', '30 min']
217
+ console.log(constant('boltzmann').mul('300 K').to('eV').toDecimal(6)) //0.025852 eV
218
+ ```
219
+
220
+ The table of units is that of mathjs (251 units, with their prefixes), with exact values: `1 inch` is exactly `2.54 cm`.
221
+ Values of different dimensions cannot be added or converted. `constant()` gives the 52 physical constants of mathjs,
222
+ with the values of CODATA 2022, and `createMath({units})` adds units of your own.
223
+
224
+ ## 9. Number theory and polynomials
225
+
226
+ ```typescript
227
+ import {ntheory, poly} from '@lakutata/math'
228
+
229
+ console.log(ntheory.factor(360)) //[{prime: 2n, exponent: 3}, {prime: 3n, exponent: 2}, {prime: 5n, exponent: 1}]
230
+ console.log(ntheory.isPrime(2n ** 61n - 1n)) //true
231
+ console.log(ntheory.convergents(ntheory.continuedFraction('pi', {terms: 4})).map(String)) //['3', '22/7', '333/106', '355/113']
232
+ console.log(poly.gcd('x^4 - 1', 'x^6 - 1').toString()) //x^2 - 1
233
+ console.log(poly.discriminant('a*x^2 + b*x + c', 'x').toString()) //-4a*c + b^2
234
+ ```
235
+
236
+ `ntheory` works on integers (bigints): divisibility, modular arithmetic, the Chinese remainder theorem, primes and
237
+ factors, sequences and counting, continued fractions. `poly` works on polynomials in any number of variables: their
238
+ greatest common divisors and least common multiples, resultants, discriminants and factors.
239
+
240
+ ## 10. Formulas as functions
241
+
242
+ An expression evaluated many times is compiled into a function of numbers, or written as code:
243
+
244
+ ```typescript
245
+ import {expression} from '@lakutata/math'
246
+
247
+ const f = expression('x^2 + sin(y)').compile(['x', 'y'])
248
+ console.log(f(2, 0)) //4
249
+ console.log(f.batch(Float64Array.of(1, 0, 2, 0, 3, 0))) //Float64Array(3) [1, 4, 9]
250
+ console.log(expression('x^2 - 5x + 6').toJavaScript()) //Math.pow(x, 2) - 5*x + 6
251
+ console.log(expression('sqrt(x^2 + y^2)').toC()) //sqrt(pow(x, 2.0) + pow(y, 2.0))
252
+ ```
253
+
254
+ `batch()` evaluates many rows of values in one call. `cse()` finds the parts that expressions share, so that the code
255
+ written from them computes each part once.
256
+
257
+ ## 11. Long operations
258
+
259
+ The operations run in the thread of JavaScript, and some of them can take long: the expansion of a large power, an
260
+ integral, thousands of digits, the factors of a large number. While one runs, the event loop waits. The operations that
261
+ can take long have an asynchronous version (`expandAsync()`, `simplifyAsync()`, `integrateAsync()`, `solveAsync()`,
262
+ `toDecimalAsync()`… and those of `linalg`, `ntheory`, `poly` and `numeric`), which runs in the threads of the addon:
263
+
264
+ ```typescript
265
+ import {createMath, MathTimeoutException} from '@lakutata/math'
266
+
267
+ const scope = createMath({timeout: 2000, concurrency: 4})
268
+ const expanded = await scope.expression('(x + y + 1)^30').expandAsync()
269
+ console.log(expanded.variables()) //['x', 'y']
270
+ try {
271
+ await scope.expression('(x + y + z + 1)^500').expandAsync({timeout: 100})
272
+ } catch (error) {
273
+ console.log(error instanceof MathTimeoutException) //true
274
+ }
275
+ const controller: AbortController = new AbortController()
276
+ setTimeout((): void => controller.abort(), 50)
277
+ await scope.expression('(x + y + z + 1)^500').expandAsync({signal: controller.signal}).catch((error: Error): void => console.log(error.name)) //MathCancelledException
278
+ ```
279
+
280
+ - The addon has as many threads as the processor has cores, minus one, or the number in the environment variable
281
+ `LAKUTATA_MATH_THREADS`. They are not the thread pool of libuv, so the files and the DNS lookups of the program do not
282
+ wait for them.
283
+ - `timeout` (an option of `createMath()`, or of one call; none by default) rejects the operation with
284
+ `MathTimeoutException`, and an aborted `signal` with `MathCancelledException`. The time spent waiting for a free
285
+ thread does not count.
286
+ - A cancelled operation stops at the next point where it checks for a cancellation, between the steps of its work. A
287
+ single step (a decomposition of a matrix by Eigen, a derivative by SymEngine) runs to its end, and the thread is free
288
+ only then: the promise is rejected at the time limit, while the work may go on for a while.
289
+ - `concurrency` (an option of `createMath()`) limits how many operations of a scope run at the same time; the others
290
+ wait their turn.
291
+
292
+ ## 12. In an application
293
+
294
+ A controller calls the package as any other code does. With a formula from a client, use an asynchronous operation and
295
+ the `signal` of the request: when the client leaves, the operation is cancelled.
296
+
297
+ ```typescript
298
+ import {Application, DTO} from 'lakutata'
299
+ import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
300
+ import {POST} from 'lakutata/decorator/ctrl'
301
+ import {Expect} from 'lakutata/decorator/dto'
302
+ import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
303
+ import {createMath, type MathScope, type Solutions} from '@lakutata/math'
304
+
305
+ const scope: MathScope = createMath({timeout: 2000})
306
+
307
+ class SolveDTO extends DTO {
308
+ @Expect(DTO.String().max(1000).required())
309
+ public equation: string
310
+ }
311
+
312
+ class SolveController extends Controller {
313
+ @POST('/solve', SolveDTO)
314
+ public async solve(input: SolveDTO): Promise<{ solutions: Solutions, set: string }> {
315
+ const solutions: Solutions = await scope.equation(input.equation).solveAsync('x', {signal: this.signal})
316
+ return {solutions: solutions, set: solutions.set.toString()}
317
+ }
318
+ }
319
+
320
+ Application.run(() => ({
321
+ id: 'solver.app',
322
+ name: 'Solver',
323
+ components: {
324
+ entrypoint: buildEntrypoints({
325
+ controllers: [SolveController],
326
+ http: expressHTTPEntrypoint({port: 8080})
327
+ })
328
+ },
329
+ bootstrap: ['entrypoint']
330
+ }))
331
+ ```
332
+
333
+ ```text
334
+ $ curl -X POST localhost:8080/solve -H 'Content-Type: application/json' -d '{"equation":"x^2 - 5x + 6 = 0"}'
335
+ {"solutions":["2","3"],"set":"{2, 3}"}
336
+ $ curl -X POST localhost:8080/solve -H 'Content-Type: application/json' -d '{"equation":"x^2 - 5x +* 6 = 0"}'
337
+ {"errno":"E_MATH_SYNTAX","errMsg":"Unexpected *"}
338
+ ```
339
+
340
+ - The exceptions of the package have no `statusCode`, so the wrong formula above is answered with the status 500 and
341
+ `{"errno": "E_MATH_SYNTAX", …}`. To answer the errors in the formulas of the clients with 400, catch `MathException`
342
+ in the action and throw an exception of the application (see [Exceptions](Exceptions.md)); leave `E_MATH_INTERNAL`, an
343
+ internal error of the addon, as it is.
344
+ - The length of a formula does not bound the work it takes: `(x + 1)^100000` is short. The time limit does.
345
+ - The values of the package become JSON through their `toJSON()`: an expression, an equation, a decimal and a value
346
+ with units give their text, solutions an array of texts, a matrix its rows. A vector is a `Float64Array`, which
347
+ `JSON.stringify()` writes as an object (`{"0": 1, "1": 1}`): send `Array.from(vector)`.
348
+ - The bundles, the executables and the installers made by `lakutata package` carry the compiled addon
349
+ (`build/Release/math.node`). Build them on each target platform, after installing the package there; the rules of
350
+ the native addons apply (see [Packaging](Packaging.md) and [NativeAddons](NativeAddons.md)).
351
+ - The package runs in Node.js `^22.13.0 || >=24.11.0`, in Bun and in the main process of Electron, which load the
352
+ addon compiled for Node.js; not in the renderers of Electron nor in the browsers. In Bun, terminating a worker thread
353
+ while operations of the addon still run in it ends the whole process with a fatal error of Node-API (a bug of Bun
354
+ 1.4).
355
+
356
+ ## 13. How it is checked, and what it does not do
357
+
358
+ The tests of the package compare its results on about 2500 cases with those of SymPy and mpmath (the formulas, the
359
+ decimals, the units), of NumPy and SciPy (the matrices, the statistics, the distributions, the numeric solutions), and
360
+ of mathjs and nerdamer; they run on macOS (arm64: Node.js 24, Bun 1.4, Electron 44), on Linux (arm64, glibc and musl:
361
+ Node.js 24, Bun 1.4) and on Windows 11 (arm64: Node.js 24).
362
+
363
+ What it does not do, whether by design or not yet:
364
+
365
+ - An integral whose fraction needs algebraic numbers to factor its denominator (`1/(x^4 + 1)`), or whose antiderivative
366
+ is neither elementary nor one of the special functions of integrals, throws `MathNotSupportedException`; it is not
367
+ kept as an unevaluated integral. `numeric.integrate()` computes the definite integral.
368
+ - `solve()` solves one equation, or a system of linear equations; a system of nonlinear equations is for
369
+ `numeric.solveSystem()`. An equation with infinitely many solutions, such as `sin(x) = 0`, gives no array of them: its
370
+ `set` only describes them (`{2pi*n + pi | n integer} or {2pi*n | n integer}`), and the operations of the sets do not
371
+ take it.
372
+ - An inequality must be made of fractions of polynomials with rational coefficients.
373
+ - The exact eigenvalues of a matrix of expressions are found up to degree four; `linalg.eigen()` gives them as numbers.
374
+ - Nothing that depends on the signs of the variables: `sqrt(x^2)` is not `abs(x)`, and a limit that depends on the
375
+ sign of a parameter (`exp(a*x)` at `inf`) is not found.
376
+ - Exact numbers have at most about 315000 digits: a larger power stays a power (`2^(10^7)`).
377
+
378
+ ## 14. Exceptions
379
+
380
+ All of them extend `MathException`. The errors in a formula carry the `position` of the character where they are found.
381
+ The README of the package lists every case.
382
+
383
+ | Exception | `errno` | When |
384
+ |---|---|---|
385
+ | `MathSyntaxException` | `E_MATH_SYNTAX` | A formula is not written correctly, an equation is given where an expression is expected, or the other way round |
386
+ | `MathUnknownFunctionException` | `E_MATH_UNKNOWN_FUNCTION` | A formula calls a function that is neither built in nor defined |
387
+ | `MathDefinitionException` | `E_MATH_DEFINITION` | A definition or a unit of `createMath()` is not valid |
388
+ | `MathUnitException` | `E_MATH_UNIT` | Units of different dimensions added, compared or converted; a unit that is not known |
389
+ | `MathDomainException` | `E_MATH_DOMAIN` | A value outside the domain of a function: a complex value asked as a number, a singular matrix inverted, a limit that does not exist |
390
+ | `MathDivisionByZeroException` | `E_MATH_DIVISION_BY_ZERO` | A division by zero while computing decimals, a modulus of 0 |
391
+ | `MathNotSupportedException` | `E_MATH_NOT_SUPPORTED` | An operation the package does not do for this formula (section 13) |
392
+ | `MathArgumentException` | `E_MATH_ARGUMENT` | An argument that is not valid: variables left where a number is asked, matrices of the wrong dimensions |
393
+ | `MathTimeoutException`, `MathCancelledException` | `E_MATH_TIMEOUT`, `E_MATH_CANCELLED` | An asynchronous operation past its time limit, or cancelled |
394
+ | `MathException` | `E_MATH_INTERNAL` | An internal error of the addon |
@@ -206,6 +206,11 @@ The application depends on the addon package, and imports it as any package (`im
206
206
  the directory layout ([Packaging](Packaging.md)). The addon is built for the platform of the machine that packs the
207
207
  application, so pack the application of each platform on that platform (a CI matrix, or a container of the
208
208
  architecture for Linux).
209
+ - **An addon package built on the framework** (it depends on `lakutata` or `@lakutata/core`, to provide a component,
210
+ for instance) is bundled with the application instead, so that there is one copy of the framework, and only its addon
211
+ and the files beside it go into the tree. The addon is shipped when it is loaded as the package of `lakutata add`
212
+ loads it (`node-gyp-build` given the directory of the package), by a `require` of its file, or by a path from the
213
+ module (`path.join(__dirname, 'prebuilds', ...)`); `lakutata package` warns of an addon loaded in another way.
209
214
  - **Electron**: the addons are copied as they are installed. The addons that `lakutata add` creates run in Electron
210
215
  without being rebuilt, as do all the addons built on Node-API; an addon built on the V8 API of Node.js must be
211
216
  installed in its build for Electron before packing (`electron-rebuild`).