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 +3 -2
- package/README.zh-CN.md +1 -1
- package/doc/en/GRPC.md +8 -2
- package/doc/en/HTTP.md +34 -0
- package/doc/en/MCP.md +4 -2
- package/doc/en/Math.md +394 -0
- package/doc/en/NativeAddons.md +5 -0
- package/doc/en/Netif.md +261 -0
- package/doc/en/Overview.md +6 -2
- package/doc/en/ServiceCalls.md +22 -3
- package/doc/zh/GRPC.md +5 -2
- package/doc/zh/HTTP.md +29 -0
- package/doc/zh/MCP.md +3 -2
- package/doc/zh/Math.md +312 -0
- package/doc/zh/NativeAddons.md +1 -0
- package/doc/zh/Netif.md +207 -0
- package/doc/zh/Overview.md +3 -1
- package/doc/zh/ServiceCalls.md +16 -3
- package/package.json +17 -13
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`, `
|
|
148
|
-
and the examples, in `doc/en/examples` and `doc/zh/examples`: whole applications
|
|
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 |
|
package/doc/en/NativeAddons.md
CHANGED
|
@@ -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`).
|