@aeroswarm/weather 0.4.0 → 0.4.2
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/CHANGELOG.md +67 -0
- package/LIMITS.md +51 -0
- package/PERFORMANCE.md +78 -0
- package/README.md +8 -0
- package/STABILITY.md +62 -0
- package/dist/api.test.d.ts +2 -0
- package/dist/api.test.d.ts.map +1 -0
- package/dist/grid.d.ts.map +1 -1
- package/dist/idw.d.ts +38 -0
- package/dist/idw.d.ts.map +1 -0
- package/dist/index.js +147 -69
- package/dist/store.d.ts.map +1 -1
- package/package.json +48 -41
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog: @aeroswarm/weather
|
|
2
|
+
|
|
3
|
+
Format: [Keep a Changelog](https://keepachangelog.com/). Versioning: see [STABILITY.md](./STABILITY.md). Entries for
|
|
4
|
+
0.1.1 to 0.4.0 were written afterwards from the git history; every one of them was released on 2026-11-02.
|
|
5
|
+
|
|
6
|
+
## Unreleased
|
|
7
|
+
|
|
8
|
+
### Changed (performance; results are the same within float rounding, checked by a test)
|
|
9
|
+
- `fieldsAt`, `blendInto`, `interpolateScalar`, `windGridFromStations`, `nudgeScalar` and `nudgeWind` are much faster:
|
|
10
|
+
distances and weights are computed once per grid cell and shared between wind, cloud and rain, with no
|
|
11
|
+
trigonometry in the inner loop. 60 stations on a 70 x 60 grid: `fieldsAt` 40 ms to 2.4 ms, `blendInto` 37 ms to
|
|
12
|
+
4 ms; 200 x 200: 340 ms to 19 ms.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `PERFORMANCE.md` (measured numbers and how to re-run them), `pnpm bench`, `pnpm size`, `size-limits.json`
|
|
16
|
+
(the build fails if the package grows past its size budget), `sideEffects: false`.
|
|
17
|
+
|
|
18
|
+
## 0.4.1 (2026-11-02)
|
|
19
|
+
|
|
20
|
+
- Documentation and tests only: `STABILITY.md`, `LIMITS.md`, `CHANGELOG.md`, public API surface test.
|
|
21
|
+
|
|
22
|
+
## 0.4.0 (2026-11-02)
|
|
23
|
+
|
|
24
|
+
- Release only: no source change since 0.3.0.
|
|
25
|
+
|
|
26
|
+
## 0.3.0 (2026-11-02)
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Operational functions: `flightCategory` (FAA thresholds), `windComponents`, `runwayAdvice`, `evaluateMinima`,
|
|
30
|
+
`upcomingChanges`, `qnhTrend`, `stationOperations`, and runway helpers (`runwayFromHeading`, `runwayFromPolygon`).
|
|
31
|
+
- Parsers for SIGMET, AIRMET, volcanic ash advisories (VAA), SNOWTAM and NIL reports.
|
|
32
|
+
- Real-data test corpus (METAR/SPECI, 200+ TAF, SIGMET, VAA) and the fixtures README.
|
|
33
|
+
|
|
34
|
+
### Behaviour
|
|
35
|
+
- A TAF change group with a malformed period (for example `TEMPO 0214/02014`) is no longer merged silently into
|
|
36
|
+
the base conditions: it is dropped and reported in `Taf.unparsed`.
|
|
37
|
+
- Several tokens that were silently ignored are now decoded or reported in `unparsed` (found by the real-data corpus).
|
|
38
|
+
|
|
39
|
+
## 0.2.0 (2026-11-02)
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- Real-data corpus and the first versions of the NIL, SIGMET, AIRMET, VAA and SNOWTAM parsers.
|
|
43
|
+
|
|
44
|
+
## 0.1.5 (2026-11-02)
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- Blend a model series with station reports (`WeatherStore.blendInto`).
|
|
48
|
+
|
|
49
|
+
## 0.1.4 (2026-11-02)
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- The `aeroswarm.weather-grid` v1 standard (`parseWeatherGridSeries`, `seriesFieldsAt`, `GRID_STANDARD.md`) and the
|
|
53
|
+
Open-Meteo adapter (`weatherGridFromOpenMeteo`, `latticePoints`).
|
|
54
|
+
|
|
55
|
+
## 0.1.3 (2026-11-02)
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
- `WeatherStore`: ingest raw messages (including AFTN), manual override, `snapshotAt`, `timeline`.
|
|
59
|
+
|
|
60
|
+
## 0.1.2 (2026-11-02)
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- Gridded wind and scalar fields (`WindGrid`, `ScalarGrid`, sampling and interpolation).
|
|
64
|
+
|
|
65
|
+
## 0.1.1 (2026-11-02)
|
|
66
|
+
|
|
67
|
+
- First release: METAR/SPECI/TAF parsers, AFTN message extraction, animation parameters.
|
package/LIMITS.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Limits of use
|
|
2
|
+
|
|
3
|
+
Read this before putting weather data in front of operators.
|
|
4
|
+
|
|
5
|
+
## Not an official source
|
|
6
|
+
|
|
7
|
+
The packages decode and display weather messages. They are not a meteorological authority. METAR, SPECI, TAF and
|
|
8
|
+
other messages issued by the responsible meteorological service are the official information, and a displayed
|
|
9
|
+
value is only as good as the message it came from and the parser that read it.
|
|
10
|
+
|
|
11
|
+
**Do not use output of these packages as the only basis for a safety decision** (take-off, landing, runway choice,
|
|
12
|
+
diversion, closing an aerodrome). Verify against the official message. Operational helpers (flight category, runway
|
|
13
|
+
advice, minima checks, alerts) are decision aids, and they assume the thresholds you configured are correct.
|
|
14
|
+
|
|
15
|
+
## What the display shows
|
|
16
|
+
|
|
17
|
+
- METAR and TAF describe one point (the aerodrome). Wind, cloud and rain drawn around an airport are a
|
|
18
|
+
representation of those reports, not the real position of clouds or rain.
|
|
19
|
+
- A grid built from several stations is an interpolation between points; between stations it is an estimate.
|
|
20
|
+
- A model forecast (Open-Meteo and others) is a model, with its own resolution and errors. Blending with reports
|
|
21
|
+
adjusts it near airports only.
|
|
22
|
+
- With a tilted or 3D map the direction of wind on screen is an approximation.
|
|
23
|
+
- There is no terrain or land mask: wind over mountains is not corrected.
|
|
24
|
+
|
|
25
|
+
## What is read, and what is not verified
|
|
26
|
+
|
|
27
|
+
Decoding is tested on real messages for METAR, SPECI, TAF (200+), international SIGMET and Tokyo VAAC
|
|
28
|
+
advisories. These are supported but have **not** been checked against real messages, only against the ICAO
|
|
29
|
+
description: AIRMET, NIL reports and SNOWTAM. Not covered by any test: Darwin VAAC, raw AFTN envelopes from a
|
|
30
|
+
live circuit, raw BMKG messages. Tokens a parser does not understand are kept in the `unparsed` field of the
|
|
31
|
+
result instead of being dropped; check it.
|
|
32
|
+
|
|
33
|
+
Known simplifications:
|
|
34
|
+
|
|
35
|
+
- METAR wind is in degrees true; runway headings are usually magnetic. Pass the magnetic variation, or the
|
|
36
|
+
crosswind is off by that angle.
|
|
37
|
+
- Flight category follows the FAA definition (ICAO does not define one).
|
|
38
|
+
- Runway headings taken from a polygon are the long axis of the shape, so they are only as accurate as the polygon.
|
|
39
|
+
- Report time has no month or year; they are taken from the time you give as "received".
|
|
40
|
+
|
|
41
|
+
## Data sources
|
|
42
|
+
|
|
43
|
+
Each source has its own licence and limits; see `SOURCES.md` in `@aeroswarm/weather-sources`. In short: the free
|
|
44
|
+
Open-Meteo endpoint is for non-commercial use only; BMKG access follows your agreement and the token is personal;
|
|
45
|
+
aviationweather.gov data is public but limited to 100 requests a minute. Respect the limits, credit the source,
|
|
46
|
+
and do not redistribute data you are not allowed to.
|
|
47
|
+
|
|
48
|
+
## No warranty
|
|
49
|
+
|
|
50
|
+
The software is provided as is, without warranty of any kind. (No licence file is included in the packages yet; the
|
|
51
|
+
licence terms are set by the package owner.)
|
package/PERFORMANCE.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Performance and size
|
|
2
|
+
|
|
3
|
+
Measured numbers, not claims. Re-run them on your own hardware; the commands are below.
|
|
4
|
+
|
|
5
|
+
**Machine used for these numbers:** Intel Core Ultra 7 155H (22 threads), 31 GB RAM, Node 22.14, Windows 11, run
|
|
6
|
+
on a laptop with other programs open (hence the spread). A weaker machine will be slower. Times are the mean of a
|
|
7
|
+
`vitest bench` run.
|
|
8
|
+
|
|
9
|
+
## Computing weather fields (`@aeroswarm/weather`)
|
|
10
|
+
|
|
11
|
+
`pnpm --filter @aeroswarm/weather bench`
|
|
12
|
+
|
|
13
|
+
| Operation | Time |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `fieldsAt`, 5 stations, 70 x 60 grid | 0.4 ms |
|
|
16
|
+
| `fieldsAt`, 60 stations, 70 x 60 grid | 2.4 ms |
|
|
17
|
+
| `fieldsAt`, 60 stations, 200 x 200 grid | 19 ms |
|
|
18
|
+
| `seriesFieldsAt` (model series, 120 hours), 70 x 60 | 1.3 ms |
|
|
19
|
+
| `seriesFieldsAt` (model series, 120 hours), 200 x 200 | 15 ms |
|
|
20
|
+
| `blendInto` (model + 60 stations), 70 x 60 | 4.1 ms |
|
|
21
|
+
| `blendInto` (model + 60 stations), 200 x 200 | 40 ms (one run varied up to 97 ms) |
|
|
22
|
+
|
|
23
|
+
Target agreed for the product: `fieldsAt` under 20 ms for a 70 x 60 grid. Met, with 8x room, up to 60 stations.
|
|
24
|
+
|
|
25
|
+
Before this change `fieldsAt` with 60 stations on 70 x 60 took about 40 ms and `blendInto` about 37 ms; the cost came from
|
|
26
|
+
recomputing distances and trigonometry for every station and every quantity. They are now computed once per cell and
|
|
27
|
+
shared (results are identical to the plain definition, checked by a test; differences are below float rounding).
|
|
28
|
+
|
|
29
|
+
**Memory of a model series** (`WeatherGridSeries`, five quantities as `Float32Array`, one frame per hour):
|
|
30
|
+
|
|
31
|
+
| Grid | 120 hours |
|
|
32
|
+
|---|---|
|
|
33
|
+
| 70 x 60 | 9.6 MB |
|
|
34
|
+
| 200 x 200 | 91.6 MB |
|
|
35
|
+
|
|
36
|
+
Ask the model source for a coarser grid or fewer quantities before asking for more hours.
|
|
37
|
+
|
|
38
|
+
## Drawing (`@aeroswarm/weather-layer`)
|
|
39
|
+
|
|
40
|
+
`pnpm --filter @aeroswarm/weather-layer bench` measures remapping a field to the screen, which runs each time the
|
|
41
|
+
map moves (at most about 10 times a second). It uses a cheap linear projection, so a real map projection costs more:
|
|
42
|
+
treat these as a lower bound.
|
|
43
|
+
|
|
44
|
+
| Screen | Wind field (24 px cells) | Colour field (8 px cells) |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| 1080p | 0.56 ms | 0.88 ms |
|
|
47
|
+
| 4K | 2.3 ms | 3.6 ms |
|
|
48
|
+
|
|
49
|
+
**Frames per second of the particle renderer have not been measured by the author.** Canvas drawing needs a real
|
|
50
|
+
browser. Run `examples/fps-benchmark.html` (instructions at the top of the file) on the target machine, and record the
|
|
51
|
+
JSON it prints. Until then there is no fps number to quote, and the earlier target (30 fps on an ordinary laptop at
|
|
52
|
+
1080p) is unverified.
|
|
53
|
+
|
|
54
|
+
Behaviours built in:
|
|
55
|
+
|
|
56
|
+
- A hidden tab draws nothing.
|
|
57
|
+
- `prefers-reduced-motion`: the overlay draws a still image. The user can turn motion back on with
|
|
58
|
+
`WeatherMotionToggle` (or `FlowRenderer.setReducedMotion`), and the renderer follows later changes of the system
|
|
59
|
+
setting.
|
|
60
|
+
- Adaptive quality: when a frame takes more than 12 ms to prepare, or frames are drawn less than two thirds as often
|
|
61
|
+
as requested, the particle count drops by 20% (down to 25%); when well under budget it rises again by 10%. The
|
|
62
|
+
current level is `overlay.quality` (1 = full). The rule is tested (`nextQualityScale`); the renderer integration needs
|
|
63
|
+
a browser and is not covered by an automated test.
|
|
64
|
+
|
|
65
|
+
## Package size
|
|
66
|
+
|
|
67
|
+
`pnpm --filter <package> size` after a build. Limits are in each package's `size-limits.json`; `prepublishOnly` fails
|
|
68
|
+
when a number goes over its limit, so growth needs a deliberate change with a note in the changelog.
|
|
69
|
+
|
|
70
|
+
| Package | All of `dist` (gzip per file) | Example: what an app pays (bundled, minified, gzip) |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `@aeroswarm/weather` | 26.0 kB | `parseMetar` only 2.8 kB, `WeatherStore` 8.3 kB, everything 19.5 kB |
|
|
73
|
+
| `@aeroswarm/weather-layer` | 24.1 kB | `legendFor` only 0.8 kB, field overlay + MapLibre adapter 4.9 kB, all of `/react-ui` 9.3 kB |
|
|
74
|
+
| `@aeroswarm/weather-sources` | 4.1 kB | `aviationWeatherSource` 1.6 kB, everything 3.1 kB |
|
|
75
|
+
|
|
76
|
+
Tree-shaking works (importing one function costs a fraction of the package), and a MapLibre app does not pay for
|
|
77
|
+
Cesium, Leaflet or RIA because each map has its own entry point. `@aeroswarm/weather-layer` now splits shared code into
|
|
78
|
+
chunks; before that, using `.` and `/react-ui` together shipped the core twice (about 12 kB gzip extra).
|
package/README.md
CHANGED
|
@@ -26,6 +26,14 @@ const snapshot = animationSnapshot({ observation: metar, forecast: taf, at: new
|
|
|
26
26
|
METAR/TAF describe one point (the aerodrome), not a weather field, so the animation is a
|
|
27
27
|
representation around the airport, not the real position of clouds or rain.
|
|
28
28
|
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
- [STABILITY.md](./STABILITY.md): what is public, semver policy, deprecation, the weather-grid standard.
|
|
32
|
+
- [LIMITS.md](./LIMITS.md): **read before operational use**: not an official source, not for safety decisions
|
|
33
|
+
without verification, what is and is not verified.
|
|
34
|
+
- [PERFORMANCE.md](./PERFORMANCE.md): measured speed, memory and bundle size, and how to re-measure.
|
|
35
|
+
- [CHANGELOG.md](./CHANGELOG.md), [GRID_STANDARD.md](./GRID_STANDARD.md).
|
|
36
|
+
|
|
29
37
|
## Gridded fields
|
|
30
38
|
|
|
31
39
|
`grid.ts` turns wind and scalar data into grids, whatever the source:
|
package/STABILITY.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Stability and versioning
|
|
2
|
+
|
|
3
|
+
Applies to `@aeroswarm/weather`, `@aeroswarm/weather-layer` and `@aeroswarm/weather-sources`.
|
|
4
|
+
|
|
5
|
+
## What is public
|
|
6
|
+
|
|
7
|
+
Public means exported from a documented entry point:
|
|
8
|
+
|
|
9
|
+
| Package | Entry points |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `@aeroswarm/weather` | `@aeroswarm/weather` |
|
|
12
|
+
| `@aeroswarm/weather-layer` | `@aeroswarm/weather-layer`, `/react`, `/react-ui`, `/maplibre`, `/leaflet`, `/cesium`, `/ria`, and the stylesheet `/react-ui/weather-ui.css` (class names `aw-*` and variables `--aw-*`) |
|
|
13
|
+
| `@aeroswarm/weather-sources` | `@aeroswarm/weather-sources` |
|
|
14
|
+
|
|
15
|
+
Not public, may change in any release: anything you reach by importing a file inside `dist/`, anything not exported
|
|
16
|
+
from an entry point, internal CSS selectors other than the documented `aw-*` classes, the exact wording of default
|
|
17
|
+
UI texts, and the text of error messages.
|
|
18
|
+
|
|
19
|
+
The runtime export names of every entry point are pinned by a test in each package (`src/api.test.ts` with a
|
|
20
|
+
snapshot in `src/__snapshots__/`). Adding, removing or renaming an export fails the test until the snapshot is
|
|
21
|
+
updated on purpose, so an API change cannot slip in unnoticed. The test covers exported names, not type shapes.
|
|
22
|
+
|
|
23
|
+
## Versioning (semver)
|
|
24
|
+
|
|
25
|
+
We follow [semver](https://semver.org/). All three packages are currently `0.x`, which semver treats as unstable;
|
|
26
|
+
we hold ourselves to this stricter rule anyway:
|
|
27
|
+
|
|
28
|
+
| Change | Release while `0.x` | Release from `1.0` |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Bug fix, no API change | patch | patch |
|
|
31
|
+
| New export, new option, new report type understood | minor | minor |
|
|
32
|
+
| Removed or renamed export, changed signature, changed default | minor, listed under **Breaking** in the changelog | major |
|
|
33
|
+
| Decoded values change because a parser bug was fixed | minor, listed under **Behaviour** in the changelog | minor |
|
|
34
|
+
| Dependency range change, new peer dependency | minor, listed in the changelog | major if it forces consumers to upgrade |
|
|
35
|
+
|
|
36
|
+
Every release has an entry in the package's `CHANGELOG.md`. A change that can break a consumer must say so there.
|
|
37
|
+
Parser fixes can change what a message decodes to; that is intended, and is why it is called out separately.
|
|
38
|
+
|
|
39
|
+
## Deprecation
|
|
40
|
+
|
|
41
|
+
- A public export is deprecated before it is removed: it keeps working, is marked `@deprecated` in its type
|
|
42
|
+
declaration with the replacement, and is listed in the changelog.
|
|
43
|
+
- While `0.x`: removed no earlier than the next minor release.
|
|
44
|
+
- From `1.0`: removed no earlier than the next major release, and not within 90 days of the deprecation.
|
|
45
|
+
|
|
46
|
+
## Peer dependencies
|
|
47
|
+
|
|
48
|
+
`@aeroswarm/weather-layer` and `@aeroswarm/weather-sources` declare `@aeroswarm/weather` as a peer dependency
|
|
49
|
+
(`>=0.4.0 <1.0.0`), so your app has one copy of it. Install it yourself next to them. React (18 or newer) is an
|
|
50
|
+
optional peer of `@aeroswarm/weather-layer`, needed only for `/react` and `/react-ui`.
|
|
51
|
+
|
|
52
|
+
## The weather-grid standard
|
|
53
|
+
|
|
54
|
+
`aeroswarm.weather-grid` version 1 (see `GRID_STANDARD.md`) is frozen: the meaning of existing fields does not
|
|
55
|
+
change, and a document valid today stays valid. Optional fields may be added in version 1. Anything that would
|
|
56
|
+
invalidate an existing document is a new standard version (2), supported alongside 1 for at least one minor
|
|
57
|
+
release of the packages. The parser rejects documents with an unknown version instead of guessing.
|
|
58
|
+
|
|
59
|
+
## Migration
|
|
60
|
+
|
|
61
|
+
Breaking changes are described in the changelog entry of the release that introduces them, with what to change.
|
|
62
|
+
When a release needs more than a few lines, a `MIGRATION.md` section is added to the same package.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.test.d.ts","sourceRoot":"","sources":["../src/api.test.ts"],"names":[],"mappings":""}
|
package/dist/grid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../src/grid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../src/grid.ts"],"names":[],"mappings":"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"}
|
package/dist/idw.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inti hitung interpolasi stasiun ke grid (internal: tidak diekspor dari paket). Jarak antar sel dan stasiun
|
|
3
|
+
* dihitung tanpa trigonometri di loop dalam, dan satu putaran jarak dipakai untuk beberapa besaran sekaligus.
|
|
4
|
+
*/
|
|
5
|
+
import type { GridBounds } from "./grid.js";
|
|
6
|
+
export declare const KM_PER_DEG_LAT = 111.32;
|
|
7
|
+
export declare const KT_TO_MS = 0.514444;
|
|
8
|
+
interface Located {
|
|
9
|
+
lat: number;
|
|
10
|
+
lon: number;
|
|
11
|
+
}
|
|
12
|
+
export interface IdwOptions {
|
|
13
|
+
bounds: GridBounds;
|
|
14
|
+
nx: number;
|
|
15
|
+
ny: number;
|
|
16
|
+
power?: number;
|
|
17
|
+
maxDistanceKm?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* IDW (bobot 1/d^p) untuk beberapa besaran di titik yang sama. Tepat di sebuah titik (jarak < 0,5 km) hasilnya
|
|
21
|
+
* persis nilai titik itu; sel yang lebih jauh dari semua titik dari `maxDistanceKm` (bawaan 400) diberi NaN.
|
|
22
|
+
*/
|
|
23
|
+
export declare function interpolateMany(points: readonly Located[], valueSets: readonly (readonly number[])[], options: IdwOptions): Float32Array[];
|
|
24
|
+
/**
|
|
25
|
+
* Tambahkan selisih (pengamatan dikurangi model) di titik-titik ke beberapa grid sekaligus: bobot dihitung
|
|
26
|
+
* sekali per sel dan dipakai untuk semua grid. Sel tanpa data (NaN) dilewati. Di luar `radius` grid tidak berubah.
|
|
27
|
+
*/
|
|
28
|
+
export declare function nudgeMany(grids: readonly Float32Array[], points: readonly Located[], deltaSets: readonly (readonly number[])[], bounds: GridBounds, nx: number, ny: number, radius: number): Float32Array[];
|
|
29
|
+
/** Komponen u/v (m/s) dari angin stasiun; arah null (VRB) dianggap tenang. */
|
|
30
|
+
export declare function stationComponents(stations: readonly {
|
|
31
|
+
speedKt: number;
|
|
32
|
+
directionDeg: number | null;
|
|
33
|
+
}[], ktToMs: number): {
|
|
34
|
+
u: number[];
|
|
35
|
+
v: number[];
|
|
36
|
+
};
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=idw.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idw.d.ts","sourceRoot":"","sources":["../src/idw.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,QAAQ,WAAY,CAAC;AAGlC,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAuBD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,OAAO,EAAE,EAC1B,SAAS,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EACzC,OAAO,EAAE,UAAU,GAClB,YAAY,EAAE,CAiDhB;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,EAAE,SAAS,OAAO,EAAE,EAC1B,SAAS,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EACzC,MAAM,EAAE,UAAU,EAClB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,GACb,YAAY,EAAE,CAuChB;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,EACrE,MAAM,EAAE,MAAM,GACb;IAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAe9B"}
|
package/dist/index.js
CHANGED
|
@@ -1350,9 +1350,128 @@ function animationSnapshot(input) {
|
|
|
1350
1350
|
return { state: animationFromConditions(conditions), ...meta };
|
|
1351
1351
|
}
|
|
1352
1352
|
|
|
1353
|
-
// src/
|
|
1354
|
-
var KT_TO_MS = 0.514444;
|
|
1353
|
+
// src/idw.ts
|
|
1355
1354
|
var KM_PER_DEG_LAT = 111.32;
|
|
1355
|
+
var KT_TO_MS = 0.514444;
|
|
1356
|
+
var RAD = Math.PI / 180;
|
|
1357
|
+
function halfLatTrig(lats) {
|
|
1358
|
+
const sin = new Float64Array(lats.length);
|
|
1359
|
+
const cos = new Float64Array(lats.length);
|
|
1360
|
+
for (let i = 0; i < lats.length; i++) {
|
|
1361
|
+
const half = lats[i] / 2 * RAD;
|
|
1362
|
+
sin[i] = Math.sin(half);
|
|
1363
|
+
cos[i] = Math.cos(half);
|
|
1364
|
+
}
|
|
1365
|
+
return { sin, cos };
|
|
1366
|
+
}
|
|
1367
|
+
function axes(bounds, nx, ny) {
|
|
1368
|
+
const lats = new Float64Array(ny);
|
|
1369
|
+
const lons = new Float64Array(nx);
|
|
1370
|
+
for (let row = 0; row < ny; row++) lats[row] = bounds.north - (bounds.north - bounds.south) * row / Math.max(1, ny - 1);
|
|
1371
|
+
for (let col = 0; col < nx; col++) lons[col] = bounds.west + (bounds.east - bounds.west) * col / Math.max(1, nx - 1);
|
|
1372
|
+
return { lats, lons };
|
|
1373
|
+
}
|
|
1374
|
+
function interpolateMany(points, valueSets, options) {
|
|
1375
|
+
const { bounds, nx, ny } = options;
|
|
1376
|
+
const power = options.power ?? 2;
|
|
1377
|
+
const maxKm = options.maxDistanceKm ?? 400;
|
|
1378
|
+
const sets = valueSets.length;
|
|
1379
|
+
const outs = valueSets.map(() => new Float32Array(nx * ny).fill(Number.NaN));
|
|
1380
|
+
const sums = new Float64Array(sets);
|
|
1381
|
+
const { lats, lons } = axes(bounds, nx, ny);
|
|
1382
|
+
const cellTrig = halfLatTrig(lats);
|
|
1383
|
+
const pointTrig = halfLatTrig(points.map((p) => p.lat));
|
|
1384
|
+
for (let row = 0; row < ny; row++) {
|
|
1385
|
+
const cellLat = lats[row];
|
|
1386
|
+
const sinC = cellTrig.sin[row];
|
|
1387
|
+
const cosC = cellTrig.cos[row];
|
|
1388
|
+
for (let col = 0; col < nx; col++) {
|
|
1389
|
+
const cellLon = lons[col];
|
|
1390
|
+
let weightSum = 0;
|
|
1391
|
+
let exact = -1;
|
|
1392
|
+
sums.fill(0);
|
|
1393
|
+
for (let i = 0; i < points.length; i++) {
|
|
1394
|
+
const p = points[i];
|
|
1395
|
+
const dLat = (cellLat - p.lat) * KM_PER_DEG_LAT;
|
|
1396
|
+
if (dLat > maxKm || dLat < -maxKm) continue;
|
|
1397
|
+
const cosMean = cosC * pointTrig.cos[i] - sinC * pointTrig.sin[i];
|
|
1398
|
+
const dLon = (cellLon - p.lon) * KM_PER_DEG_LAT * cosMean;
|
|
1399
|
+
const d = Math.sqrt(dLat * dLat + dLon * dLon);
|
|
1400
|
+
if (d > maxKm) continue;
|
|
1401
|
+
if (d < 0.5) {
|
|
1402
|
+
exact = i;
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
const w = power === 2 ? 1 / (d * d) : 1 / d ** power;
|
|
1406
|
+
weightSum += w;
|
|
1407
|
+
for (let k = 0; k < sets; k++) sums[k] = sums[k] + w * valueSets[k][i];
|
|
1408
|
+
}
|
|
1409
|
+
const index = row * nx + col;
|
|
1410
|
+
for (let k = 0; k < sets; k++) {
|
|
1411
|
+
outs[k][index] = exact >= 0 ? valueSets[k][exact] : weightSum > 0 ? sums[k] / weightSum : Number.NaN;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return outs;
|
|
1416
|
+
}
|
|
1417
|
+
var influence = (distance, radius) => distance >= radius ? 0 : (radius * radius - distance * distance) / (radius * radius + distance * distance);
|
|
1418
|
+
function nudgeMany(grids, points, deltaSets, bounds, nx, ny, radius) {
|
|
1419
|
+
const outs = grids.map((g) => Float32Array.from(g));
|
|
1420
|
+
if (points.length === 0) return outs;
|
|
1421
|
+
const sets = grids.length;
|
|
1422
|
+
const weighted = new Float64Array(sets);
|
|
1423
|
+
const { lats, lons } = axes(bounds, nx, ny);
|
|
1424
|
+
const cellTrig = halfLatTrig(lats);
|
|
1425
|
+
const pointTrig = halfLatTrig(points.map((p) => p.lat));
|
|
1426
|
+
for (let row = 0; row < ny; row++) {
|
|
1427
|
+
const cellLat = lats[row];
|
|
1428
|
+
const sinC = cellTrig.sin[row];
|
|
1429
|
+
const cosC = cellTrig.cos[row];
|
|
1430
|
+
for (let col = 0; col < nx; col++) {
|
|
1431
|
+
const cellLon = lons[col];
|
|
1432
|
+
const index = row * nx + col;
|
|
1433
|
+
let weights = 0;
|
|
1434
|
+
weighted.fill(0);
|
|
1435
|
+
for (let i = 0; i < points.length; i++) {
|
|
1436
|
+
const p = points[i];
|
|
1437
|
+
const dLat = (cellLat - p.lat) * KM_PER_DEG_LAT;
|
|
1438
|
+
if (dLat >= radius || dLat <= -radius) continue;
|
|
1439
|
+
const cosMean = cosC * pointTrig.cos[i] - sinC * pointTrig.sin[i];
|
|
1440
|
+
const dLon = (cellLon - p.lon) * KM_PER_DEG_LAT * cosMean;
|
|
1441
|
+
const w = influence(Math.sqrt(dLat * dLat + dLon * dLon), radius);
|
|
1442
|
+
if (w === 0) continue;
|
|
1443
|
+
weights += w;
|
|
1444
|
+
for (let k = 0; k < sets; k++) weighted[k] = weighted[k] + w * deltaSets[k][i];
|
|
1445
|
+
}
|
|
1446
|
+
if (weights <= 0) continue;
|
|
1447
|
+
const scale = Math.min(1, weights);
|
|
1448
|
+
for (let k = 0; k < sets; k++) {
|
|
1449
|
+
const out = outs[k];
|
|
1450
|
+
if (Number.isNaN(out[index])) continue;
|
|
1451
|
+
out[index] = out[index] + weighted[k] / weights * scale;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return outs;
|
|
1456
|
+
}
|
|
1457
|
+
function stationComponents(stations, ktToMs) {
|
|
1458
|
+
const u = [];
|
|
1459
|
+
const v = [];
|
|
1460
|
+
for (const s of stations) {
|
|
1461
|
+
if (s.directionDeg === null) {
|
|
1462
|
+
u.push(0);
|
|
1463
|
+
v.push(0);
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const rad = s.directionDeg * Math.PI / 180;
|
|
1467
|
+
const speed = s.speedKt * ktToMs;
|
|
1468
|
+
u.push(-speed * Math.sin(rad));
|
|
1469
|
+
v.push(-speed * Math.cos(rad));
|
|
1470
|
+
}
|
|
1471
|
+
return { u, v };
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// src/grid.ts
|
|
1356
1475
|
var speedOf = (u, v) => Math.hypot(u, v);
|
|
1357
1476
|
function directionFromUV(u, v) {
|
|
1358
1477
|
return (Math.atan2(-u, -v) * 180 / Math.PI + 360) % 360;
|
|
@@ -1367,37 +1486,9 @@ function pointOf(bounds, nx, ny, col, row) {
|
|
|
1367
1486
|
lat: bounds.north - (bounds.north - bounds.south) * row / Math.max(1, ny - 1)
|
|
1368
1487
|
};
|
|
1369
1488
|
}
|
|
1370
|
-
function distanceKm(a, b) {
|
|
1371
|
-
const dLat = (a.lat - b.lat) * KM_PER_DEG_LAT;
|
|
1372
|
-
const dLon = (a.lon - b.lon) * KM_PER_DEG_LAT * Math.cos((a.lat + b.lat) / 2 * (Math.PI / 180));
|
|
1373
|
-
return Math.hypot(dLat, dLon);
|
|
1374
|
-
}
|
|
1375
1489
|
function interpolateScalar(points, options) {
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
const maxKm = options.maxDistanceKm ?? 400;
|
|
1379
|
-
const values = new Float32Array(nx * ny).fill(Number.NaN);
|
|
1380
|
-
for (let row = 0; row < ny; row++) {
|
|
1381
|
-
for (let col = 0; col < nx; col++) {
|
|
1382
|
-
const cell = pointOf(bounds, nx, ny, col, row);
|
|
1383
|
-
let weightSum = 0;
|
|
1384
|
-
let sum = 0;
|
|
1385
|
-
let exact = null;
|
|
1386
|
-
for (const p of points) {
|
|
1387
|
-
const d = distanceKm(cell, p);
|
|
1388
|
-
if (d > maxKm) continue;
|
|
1389
|
-
if (d < 0.5) {
|
|
1390
|
-
exact = p.value;
|
|
1391
|
-
break;
|
|
1392
|
-
}
|
|
1393
|
-
const w = 1 / d ** power;
|
|
1394
|
-
weightSum += w;
|
|
1395
|
-
sum += w * p.value;
|
|
1396
|
-
}
|
|
1397
|
-
values[row * nx + col] = exact ?? (weightSum > 0 ? sum / weightSum : Number.NaN);
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
return { bounds, nx, ny, values };
|
|
1490
|
+
const [values] = interpolateMany(points, [points.map((p) => p.value)], options);
|
|
1491
|
+
return { bounds: options.bounds, nx: options.nx, ny: options.ny, values };
|
|
1401
1492
|
}
|
|
1402
1493
|
function windGridFromStations(stations, options) {
|
|
1403
1494
|
const components = stations.map((s) => {
|
|
@@ -1405,9 +1496,8 @@ function windGridFromStations(stations, options) {
|
|
|
1405
1496
|
const { u: u2, v: v2 } = s.directionDeg === null ? { u: 0, v: 0 } : uvFromSpeedDirection(speedMs, s.directionDeg);
|
|
1406
1497
|
return { lat: s.lat, lon: s.lon, u: u2, v: v2 };
|
|
1407
1498
|
});
|
|
1408
|
-
const u =
|
|
1409
|
-
|
|
1410
|
-
return { bounds: options.bounds, nx: options.nx, ny: options.ny, u: u.values, v: v.values };
|
|
1499
|
+
const [u, v] = interpolateMany(components, [components.map((c) => c.u), components.map((c) => c.v)], options);
|
|
1500
|
+
return { bounds: options.bounds, nx: options.nx, ny: options.ny, u, v };
|
|
1411
1501
|
}
|
|
1412
1502
|
function bilinear(values, bounds, nx, ny, lon, lat) {
|
|
1413
1503
|
if (lon < bounds.west || lon > bounds.east || lat < bounds.south || lat > bounds.north) return null;
|
|
@@ -1434,51 +1524,32 @@ function sampleWind(grid, lon, lat) {
|
|
|
1434
1524
|
const v = bilinear(grid.v, grid.bounds, grid.nx, grid.ny, lon, lat);
|
|
1435
1525
|
return u === null || v === null ? null : { u, v };
|
|
1436
1526
|
}
|
|
1437
|
-
var influence = (distance, radius) => distance >= radius ? 0 : (radius * radius - distance * distance) / (radius * radius + distance * distance);
|
|
1438
|
-
function nudgeValues(values, bounds, nx, ny, deltas, radius) {
|
|
1439
|
-
const out = Float32Array.from(values);
|
|
1440
|
-
if (deltas.length === 0) return out;
|
|
1441
|
-
for (let row = 0; row < ny; row++) {
|
|
1442
|
-
for (let col = 0; col < nx; col++) {
|
|
1443
|
-
const index = row * nx + col;
|
|
1444
|
-
if (Number.isNaN(out[index])) continue;
|
|
1445
|
-
const cell = pointOf(bounds, nx, ny, col, row);
|
|
1446
|
-
let weights = 0;
|
|
1447
|
-
let weighted = 0;
|
|
1448
|
-
for (const d of deltas) {
|
|
1449
|
-
const w = influence(distanceKm(cell, d), radius);
|
|
1450
|
-
weights += w;
|
|
1451
|
-
weighted += w * d.delta;
|
|
1452
|
-
}
|
|
1453
|
-
if (weights > 0) out[index] = out[index] + weighted / weights * Math.min(1, weights);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
return out;
|
|
1457
|
-
}
|
|
1458
1527
|
function nudgeScalar(grid, points, options = {}) {
|
|
1528
|
+
const located = [];
|
|
1459
1529
|
const deltas = [];
|
|
1460
1530
|
for (const p of points) {
|
|
1461
1531
|
const model = sampleScalar(grid, p.lon, p.lat);
|
|
1462
|
-
if (model
|
|
1532
|
+
if (model === null) continue;
|
|
1533
|
+
located.push({ lat: p.lat, lon: p.lon });
|
|
1534
|
+
deltas.push(p.value - model);
|
|
1463
1535
|
}
|
|
1464
|
-
|
|
1536
|
+
const [values] = nudgeMany([grid.values], located, [deltas], grid.bounds, grid.nx, grid.ny, options.radiusKm ?? 80);
|
|
1537
|
+
return { ...grid, values };
|
|
1465
1538
|
}
|
|
1466
1539
|
function nudgeWind(grid, stations, options = {}) {
|
|
1540
|
+
const located = [];
|
|
1467
1541
|
const du = [];
|
|
1468
1542
|
const dv = [];
|
|
1469
1543
|
for (const s of stations) {
|
|
1470
1544
|
const model = sampleWind(grid, s.lon, s.lat);
|
|
1471
1545
|
if (!model) continue;
|
|
1472
1546
|
const obs = s.directionDeg === null ? { u: 0, v: 0 } : uvFromSpeedDirection(s.speedKt * KT_TO_MS, s.directionDeg);
|
|
1473
|
-
|
|
1474
|
-
|
|
1547
|
+
located.push({ lat: s.lat, lon: s.lon });
|
|
1548
|
+
du.push(obs.u - model.u);
|
|
1549
|
+
dv.push(obs.v - model.v);
|
|
1475
1550
|
}
|
|
1476
|
-
const
|
|
1477
|
-
return {
|
|
1478
|
-
...grid,
|
|
1479
|
-
u: nudgeValues(grid.u, grid.bounds, grid.nx, grid.ny, du, radius),
|
|
1480
|
-
v: nudgeValues(grid.v, grid.bounds, grid.nx, grid.ny, dv, radius)
|
|
1481
|
-
};
|
|
1551
|
+
const [u, v] = nudgeMany([grid.u, grid.v], located, [du, dv], grid.bounds, grid.nx, grid.ny, options.radiusKm ?? 80);
|
|
1552
|
+
return { ...grid, u, v };
|
|
1482
1553
|
}
|
|
1483
1554
|
function blendWindGrids(a, b, t) {
|
|
1484
1555
|
if (a.nx !== b.nx || a.ny !== b.ny) throw new Error("Grid angin harus berukuran sama");
|
|
@@ -2282,12 +2353,19 @@ var WeatherStore = class {
|
|
|
2282
2353
|
fieldsAt(at, grid, options = {}) {
|
|
2283
2354
|
const points = this.pointsAt(at, options);
|
|
2284
2355
|
if (points.stations.length === 0) return null;
|
|
2356
|
+
const { u, v } = stationComponents(points.winds, KT_TO_MS);
|
|
2357
|
+
const [windU, windV, cloud, rain] = interpolateMany(
|
|
2358
|
+
points.winds,
|
|
2359
|
+
[u, v, points.clouds.map((c) => c.value), points.rains.map((r) => r.value)],
|
|
2360
|
+
grid
|
|
2361
|
+
);
|
|
2362
|
+
const { bounds, nx, ny } = grid;
|
|
2285
2363
|
return {
|
|
2286
2364
|
at,
|
|
2287
2365
|
stations: points.stations,
|
|
2288
|
-
wind:
|
|
2289
|
-
cloud:
|
|
2290
|
-
rain:
|
|
2366
|
+
wind: { bounds, nx, ny, u: windU, v: windV },
|
|
2367
|
+
cloud: { bounds, nx, ny, values: cloud },
|
|
2368
|
+
rain: { bounds, nx, ny, values: rain },
|
|
2291
2369
|
temperature: points.temperatures.length > 0 ? interpolateScalar(points.temperatures, grid) : null
|
|
2292
2370
|
};
|
|
2293
2371
|
}
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,41 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@aeroswarm/weather",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Decode raw AFTN/METAR/SPECI/TAF messages and turn them into animation parameters.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"import": "./dist/index.js",
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"main": "./dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"GRID_STANDARD.md"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"release
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"release
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@aeroswarm/weather",
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "Decode raw AFTN/METAR/SPECI/TAF messages and turn them into animation parameters.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"main": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"GRID_STANDARD.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"STABILITY.md",
|
|
20
|
+
"LIMITS.md",
|
|
21
|
+
"PERFORMANCE.md"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"registry": "https://registry.npmjs.org/"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup src/index.ts --format esm --clean && tsc --emitDeclarationOnly",
|
|
29
|
+
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
30
|
+
"test": "vitest run --passWithNoTests",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"clean": "rm -rf dist",
|
|
33
|
+
"prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build && pnpm run size",
|
|
34
|
+
"release": "release-it",
|
|
35
|
+
"release:dry": "release-it --dry-run",
|
|
36
|
+
"release:patch": "release-it patch --no-git.requireCleanWorkingDir",
|
|
37
|
+
"release:minor": "release-it minor --no-git.requireCleanWorkingDir",
|
|
38
|
+
"release:major": "release-it major --no-git.requireCleanWorkingDir",
|
|
39
|
+
"bench": "vitest bench --run",
|
|
40
|
+
"size": "node ../../../scripts/package-size.mjs"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"release-it": "^18.1.2",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"vitest": "^4.1.6"
|
|
46
|
+
},
|
|
47
|
+
"sideEffects": false
|
|
48
|
+
}
|