@mega-yfue/eufy-sdk 0.2.0-beta.9 → 0.3.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/client/device-registry.d.ts +14 -28
  2. package/dist/client/eufy-mega.d.ts +11 -6
  3. package/dist/client/types.d.ts +6 -2
  4. package/dist/core/contracts.d.ts +23 -27
  5. package/dist/core/crypto.d.ts +10 -0
  6. package/dist/core/index.d.ts +1 -0
  7. package/dist/core/solix-types.d.ts +121 -0
  8. package/dist/core/store.d.ts +38 -10
  9. package/dist/index.js +2690 -485
  10. package/dist/index.js.map +4 -4
  11. package/dist/model/capabilities/access.d.ts +22 -3
  12. package/dist/model/capabilities/arming.d.ts +4 -0
  13. package/dist/model/capabilities/battery.d.ts +32 -4
  14. package/dist/model/capabilities/contact.d.ts +4 -0
  15. package/dist/model/capabilities/doorbell.d.ts +24 -14
  16. package/dist/model/capabilities/index.d.ts +14 -3
  17. package/dist/model/capabilities/lock.d.ts +15 -12
  18. package/dist/model/capabilities/ptz.d.ts +6 -2
  19. package/dist/model/capabilities/solix.d.ts +173 -0
  20. package/dist/model/capabilities/types.d.ts +60 -10
  21. package/dist/model/capabilities/vacuum-clean.d.ts +59 -0
  22. package/dist/model/classify.d.ts +3 -1
  23. package/dist/model/device-family.d.ts +2 -1
  24. package/dist/model/device-types.d.ts +1 -0
  25. package/dist/model/device.d.ts +15 -0
  26. package/dist/model/index.d.ts +5 -0
  27. package/dist/model/solix-catalog.d.ts +25 -0
  28. package/dist/model/solix-device.d.ts +137 -0
  29. package/dist/model/solix-family.d.ts +31 -0
  30. package/dist/model/solix-site.d.ts +70 -0
  31. package/dist/transport/ff09.d.ts +7 -0
  32. package/dist/transport/http/decodeImageV2.d.ts +8 -14
  33. package/dist/transport/http/index.d.ts +1 -0
  34. package/dist/transport/http/jpeg-scan.d.ts +59 -0
  35. package/dist/transport/http/media-download.d.ts +3 -0
  36. package/dist/transport/http/mega-client.d.ts +89 -9
  37. package/dist/transport/http/solix-client.d.ts +270 -0
  38. package/dist/transport/http/solix-constants.d.ts +56 -0
  39. package/dist/transport/media-failure.d.ts +48 -0
  40. package/dist/transport/mqtt/index.d.ts +3 -0
  41. package/dist/transport/mqtt/secure-mqtt.d.ts +14 -1
  42. package/dist/transport/mqtt/solix-mqtt.d.ts +360 -0
  43. package/dist/transport/mqtt/topics.d.ts +30 -0
  44. package/dist/transport/p2p/command-router.d.ts +131 -19
  45. package/dist/transport/p2p/live-stream.d.ts +5 -4
  46. package/dist/transport/p2p/live-trace.d.ts +32 -5
  47. package/dist/transport/p2p/media.d.ts +2 -2
  48. package/dist/transport/p2p/p2p-session.d.ts +9 -0
  49. package/dist/transport/p2p/session-manager.d.ts +57 -23
  50. package/dist/transport/p2p/shared-live-source.d.ts +10 -1
  51. package/dist/transport/p2p/station-channels.d.ts +54 -0
  52. package/dist/transport/stored-image-cache.d.ts +7 -1
  53. package/package.json +5 -5
@@ -67,7 +67,7 @@ export interface LiveStreamOptions {
67
67
  * How long an attached stream tolerates silence on its own channel before re-asserting again, in ms.
68
68
  *
69
69
  * The re-assert is settled by the first own-channel frame, because settling it is what stops two attached
70
- * streams contending on a station that serves one camera at a time. Silence for this long says the station
70
+ * streams contending over one session, which serves one camera at a time. Silence for this long says the station
71
71
  * is no longer serving this camera, which is the only condition the re-assert was for. Defaults to twice
72
72
  * the keepalive interval, so a stream whose media flows never reaches it.
73
73
  */
@@ -129,8 +129,9 @@ export declare class LiveStream extends EventEmitter {
129
129
  * Stop re-issuing the media start once this camera's own media has arrived, on an attached camera.
130
130
  *
131
131
  * The nudge differs by topology and only one branch is a ping: an own-session camera sends a small
132
- * keepalive, while an attached camera has no such state and re-sends the FULL media start. On a station that
133
- * serves one camera at a time that restart re-asserts this channel against whatever else is warm, so two
132
+ * keepalive, while an attached camera has no such state and re-sends the FULL media start. Over one session,
133
+ * which serves one camera at a time, that restart re-asserts this channel against whatever else is warm on
134
+ * it, so two
134
135
  * attached streams restart every interval and contend for the station continuously — measured on a real base
135
136
  * as a full start every 3 s from each.
136
137
  *
@@ -185,7 +186,7 @@ export declare class LiveStream extends EventEmitter {
185
186
  *
186
187
  * The match is UNCONDITIONAL, however long a station serves another camera instead of this one.
187
188
  *
188
- * A station serving one camera at a time hands a newly opened camera nothing but its sibling's frames until
189
+ * One session serving one camera at a time hands a newly opened camera nothing but its sibling's frames until
189
190
  * it switches, so "no media of my own yet, plenty for someone else" is what an ordinary handover looks like
190
191
  * and does not distinguish a station that tags differently from one that is simply busy.
191
192
  *
@@ -63,6 +63,20 @@ export type LiveTrace =
63
63
  phase: "sequence-restart";
64
64
  dataType: number;
65
65
  }
66
+ /**
67
+ * Which lookup channels a connection can ask for the station on, before it asks.
68
+ *
69
+ * A station is found by a local lookup, by a cloud lookup, or by both, and each needs something the other
70
+ * does not: the local one needs the station on this link, the cloud one needs both a key for the station and
71
+ * an address to ask. A connect that had one channel failed for that channel's reason alone, and a connect
72
+ * that had neither could not have succeeded — outcomes a station that is switched off is otherwise
73
+ * indistinguishable from, because nothing else in a failed connect states what was even attempted.
74
+ */
75
+ | {
76
+ phase: "lookup-channels";
77
+ local: boolean;
78
+ cloud: boolean;
79
+ }
66
80
  /**
67
81
  * Work on a station is holding for its session to connect, with the milliseconds it will wait.
68
82
  *
@@ -150,17 +164,30 @@ export type LiveTrace =
150
164
  /**
151
165
  * A station was resolved for a call, stating what the caller's device is on it and whose station it is.
152
166
  *
153
- * Emitted before anything is sent, so it is the only account of the intended topology on a call that fails
154
- * during resolution: an attached camera's media start has no unencrypted form, so whether a device was taken
155
- * as attached decides what its failure means. `stationAdmin` states whether the signed-in account is the
156
- * station's administrator, which is what a key the account cannot resolve turns on; `unstated` is a device
157
- * record that names no administrator, which is not the same as naming another.
167
+ * Emitted before the session is waited on, so a station that is never reached still has this record: an
168
+ * attached camera's media start has no unencrypted form, so whether a device was taken as attached decides
169
+ * what its failure means. `stationAdmin` states whether the signed-in account is the station's
170
+ * administrator, which is what a key the account cannot resolve turns on; `unstated` is a device record
171
+ * that names no administrator, which is not the same as naming another. `stationModel` is the model of the
172
+ * station the call resolved — the base's for an attached camera, the device's own where it is its own
173
+ * station — absent where that record states none; without it a base this SDK reaches differently is
174
+ * indistinguishable from one that is switched off.
158
175
  */
159
176
  | {
160
177
  phase: "station-resolved";
161
178
  topology: "attached" | "own";
162
179
  channel: number;
163
180
  stationAdmin: "self" | "other" | "unstated";
181
+ stationModel?: string;
182
+ }
183
+ /**
184
+ * The call's device has no usable channel on the station it resolved: its record states none (`missing`), or
185
+ * another device attached to the same station states the same one (`shared`). The call is refused with
186
+ * `DeviceChannelUnresolvedError` and nothing is sent.
187
+ */
188
+ | {
189
+ phase: "station-channel-unresolved";
190
+ issue: "missing" | "shared";
164
191
  }
165
192
  /** A shared source began warming, with the interval it re-issues on and the deadline it fails at. */
166
193
  | {
@@ -36,8 +36,8 @@ export declare function openLiveStream(session: P2PSession, opts?: LiveStreamOpt
36
36
  * image, so the image is its source of truth.
37
37
  *
38
38
  * The consumer is detached the moment the collected run is complete, and the decode that follows holds no
39
- * station: it works on bytes already in memory. A station serves one camera at a time and the SDK refuses a
40
- * second channel on one that is busy, so a still that kept its pull attached across its own decode would deny
39
+ * station: it works on bytes already in memory. One session serves one camera at a time and a still never
40
+ * opens a second one, so a still that kept its pull attached across its own decode would deny
41
41
  * that station to every live request for the length of an FFmpeg run — measured on a real base as a live
42
42
  * request refused 370ms after the still it was waiting on had already collected everything it needed.
43
43
  *
@@ -16,6 +16,15 @@
16
16
  import { EventEmitter } from "node:events";
17
17
  import { type Address, type P2PDataFrameHeader } from "./codec.js";
18
18
  import { type Logger } from "../../core/logger.js";
19
+ /**
20
+ * How long a station is given to answer a lookup before the connection gives up on it and closes.
21
+ *
22
+ * The whole deadline for reaching a station: the lookups are re-sent every second until one is answered, and
23
+ * a connection that reaches this closes itself, so nothing addressed to that station can succeed afterwards.
24
+ * Published because it bounds every wait on a session connecting — a second number for the same deadline
25
+ * elsewhere would outlive the connection it waits on and charge the difference to every failure.
26
+ */
27
+ export declare const CONNECT_TIMEOUT_MS = 15000;
19
28
  /**
20
29
  * The channel a command addresses the station itself on, rather than one of its cameras, and the value a
21
30
  * session's channel-taking methods resolve an omitted channel to.
@@ -26,24 +26,35 @@ export interface SessionManagerOpts {
26
26
  batteryIdleMs?: number;
27
27
  /** Keepalive a single command holds after dispatch (ms). Default {@link COMMAND_KEEPALIVE_MS}. */
28
28
  commandKeepAliveMs?: number;
29
- /** Power tier per station serial — injected by the facade (no model import). Default: everything `wired`. */
29
+ /**
30
+ * Power tier per station serial — injected by the facade (no model import). Default: everything
31
+ * `wired`. Asked about the STATION a session connects to, never the key it is filed under, so every
32
+ * session to one station gets that station's idle window.
33
+ */
30
34
  poweredFor?: (parentSn: string) => PowerTier;
31
35
  /**
32
- * Called after the manager closes a station on its OWN initiative — an elapsed idle window, or a
33
- * deferred reset falling due.
36
+ * Called with the KEY of a session the manager closed on its OWN initiative — an elapsed idle window,
37
+ * or a deferred reset falling due. The key, not the station: several sessions can share a station and
38
+ * only the one that closed is stale, so an owner told the station would tear down connections that are
39
+ * still serving.
34
40
  *
35
41
  * Those two are the only closes with no caller to follow up: everything riding the session is stale the
36
42
  * moment it goes, and only the owner knows what that is. A close a caller asked for is that caller's to
37
43
  * clean up after, which is why this does not fire for {@link SessionManager.close},
38
44
  * {@link SessionManager.closeAll}, or a superseded open.
39
45
  */
40
- onAutoClose?: (parentSn: string) => void;
46
+ onAutoClose?: (key: string) => void;
41
47
  /** Diagnostics sink for the lifecycle transitions (open / idle-arm / detach). Omit for silence. */
42
48
  logger?: Logger;
43
49
  }
44
50
  /**
45
- * Manages P2P sessions keyed by **parent station serial**. The router builds/wires the actual
46
- * `P2PSession` (it owns the socket + event fan-out); this decides open/close timing.
51
+ * Manages P2P sessions by **key**, each recording the station it connects to. The router builds and
52
+ * wires the actual `P2PSession` (it owns the socket and event fan-out); this decides open/close timing.
53
+ *
54
+ * A key is the station's serial for the one session that carries its control traffic and its events.
55
+ * Where a station has to serve more than one camera at once it also holds a session per camera, filed
56
+ * under a key of the router's choosing and recording the same station — so each has its own refcount and
57
+ * its own idle window, and releasing one never disturbs another.
47
58
  */
48
59
  export declare class SessionManager {
49
60
  private readonly opts;
@@ -53,23 +64,40 @@ export declare class SessionManager {
53
64
  private readonly logger;
54
65
  constructor(opts?: SessionManagerOpts);
55
66
  /** The live session for a station, or `undefined` if not open. */
56
- get(parentSn: string): P2PSession | undefined;
57
- /** Serials of stations with a live session. */
67
+ get(key: string): P2PSession | undefined;
68
+ /** Keys of the open sessions. */
58
69
  keys(): string[];
59
- /** A plain `Map<parentSn, P2PSession>` snapshot of the live sessions (for `getSessions()` / tests). */
70
+ /** A plain `Map<key, P2PSession>` snapshot of the open sessions (for `getSessions()` / tests). */
60
71
  liveSessions(): Map<string, P2PSession>;
61
- /** Get or create the lifecycle entry for a station. */
72
+ /**
73
+ * Get or create the lifecycle entry under `key`, recording which station it connects to. An existing
74
+ * entry keeps the station it was opened with — the key owns one connection for its lifetime, and a
75
+ * later caller passing a different station would otherwise re-point a live entry's power tier.
76
+ */
62
77
  private entry;
63
- /** Register an already-built session for test seeding or an externally assembled connection. */
64
- register(parentSn: string, session: P2PSession): void;
65
78
  /**
66
- * Ensure a session to `parentSn` is open, building it via `factory` if cold. Concurrent calls for the
79
+ * Register an already-built session for test seeding or an externally assembled connection.
80
+ *
81
+ * `station` defaults to the key, which is safe HERE and nowhere else in this class: the paths that open a
82
+ * session under a key that is not a station serial all go through {@link acquire}, which requires it. A
83
+ * caller seeding one under such a key must pass it.
84
+ */
85
+ register(key: string, session: P2PSession, station?: string): void;
86
+ /**
87
+ * Ensure a session to `key` is open, building it via `factory` if cold. Concurrent calls for the
67
88
  * same cold station share ONE connect (the `connecting` promise); `factory` builds + wires + awaits
68
89
  * `connect()` and resolves the connected session.
69
90
  */
70
- acquire(parentSn: string, factory: (register: (session: P2PSession) => void) => Promise<P2PSession>): Promise<P2PSession>;
71
- /** Add a reason to stay connected; cancels a pending idle-close. */
72
- retain(parentSn: string): void;
91
+ acquire(key: string, factory: (register: (session: P2PSession) => void) => Promise<P2PSession>, station: string): Promise<P2PSession>;
92
+ /**
93
+ * Add a reason to stay connected; cancels a pending idle-close.
94
+ *
95
+ * Refused when nothing is open under `key`, for the reason {@link release} gives in the other direction: a
96
+ * retain names a session that was acquired, and one that names nothing would file an entry with no
97
+ * connection behind it. {@link hold} is the path that legitimately creates one, and it opens the entry
98
+ * itself before retaining it.
99
+ */
100
+ retain(key: string): void;
73
101
  /**
74
102
  * Release a reason; arm the idle-close when the last one goes.
75
103
  *
@@ -78,22 +106,28 @@ export declare class SessionManager {
78
106
  * from scratch or complete a deferred reset a real viewer has not yet earned. Clamping to zero did
79
107
  * both silently.
80
108
  */
81
- release(parentSn: string): void;
109
+ release(key: string): void;
82
110
  /**
83
111
  * Hold a session warm for `commandKeepAliveMs` after a control command, then release. A burst of
84
112
  * commands each re-holds before the previous release fires, so the session never idles mid-burst.
85
113
  */
86
- bumpCommand(parentSn: string): void;
114
+ bumpCommand(key: string, station: string): void;
87
115
  /**
88
- * Retain a station and release it again after `ms` — the primitive behind command-keepalive and event
116
+ * Retain a session and release it again after `ms` — the primitive behind command-keepalive and event
89
117
  * pre-warm, and the only way to hold one open without an attachment to release it.
90
118
  *
119
+ * `station` is required rather than defaulted from the key, because this is the one path that can
120
+ * CREATE an entry: a pre-warm takes its hold before the open. An entry filed under a media key with
121
+ * that key as its own station would be asked for the power tier of a serial that does not exist, be
122
+ * answered `wired`, and never idle-detach — which on a battery station is the drain this class exists
123
+ * to prevent, and is invisible until the battery is flat.
124
+ *
91
125
  * The timer is owned by the entry, so {@link discard} cancels it. That ownership is the point: keyed
92
126
  * only by serial, an expiring hold would otherwise outlive the entry it was taken on and release a
93
127
  * retain counted by the SUCCESSOR entry — dropping a live viewer's count and arming an idle-detach
94
128
  * underneath it.
95
129
  */
96
- hold(parentSn: string, ms: number): void;
130
+ hold(key: string, ms: number, station: string): void;
97
131
  /**
98
132
  * Arm the idle-close timer for a station whose retain count just reached zero. A wired station with
99
133
  * an infinite window is left persistent (no timer). Any subsequent {@link retain} cancels it.
@@ -123,9 +157,9 @@ export declare class SessionManager {
123
157
  */
124
158
  private autoClose;
125
159
  /** Drop a station's entry + timer (called from the session's `close` handler). Idempotent. */
126
- remove(parentSn: string): void;
160
+ remove(key: string): void;
127
161
  /** Close one station now and discard its lifecycle entry. */
128
- close(parentSn: string): Promise<void>;
162
+ close(key: string): Promise<void>;
129
163
  /**
130
164
  * Reset once every viewer detaches, ignoring only expiring holds.
131
165
  *
@@ -136,7 +170,7 @@ export declare class SessionManager {
136
170
  * Both branches close through {@link autoClose}: the caller asked for a recycle, not for the station's
137
171
  * live sources to be dropped, so it does not clean up after one — exactly like the idle path.
138
172
  */
139
- resetWhenUnused(parentSn: string): Promise<void>;
173
+ resetWhenUnused(key: string): Promise<void>;
140
174
  /** Settle a discarded entry's reset callers with the same outcome as its session close. */
141
175
  private settleReset;
142
176
  /** Close one discarded entry and settle only its own reset callers before preserving any failure. */
@@ -10,7 +10,7 @@ export interface SharedLiveSourceOptions {
10
10
  * `.start()` itself.
11
11
  *
12
12
  * `ctx.reassertWanted` answers whether this pull still has anyone attached. A stream that re-asserts a
13
- * channel to hold it open should consult it, so a pull nothing is watching stops competing for a station
13
+ * channel to hold it open should consult it, so a pull nothing is watching stops competing for a session
14
14
  * that serves one camera at a time.
15
15
  */
16
16
  makeStream: (ctx: {
@@ -73,6 +73,15 @@ export interface SharedLiveSourceOptions {
73
73
  * an owner does in response to this callback.
74
74
  */
75
75
  onStartFailed?: () => void;
76
+ /**
77
+ * The pull has ended and will not resume: the linger elapsed, the battery budget ran out, or the source
78
+ * was torn down. A later attach builds a fresh stream rather than reviving this one.
79
+ *
80
+ * Distinct from {@link onIdle} by what is still running. `onIdle` fires at the last detach, while the
81
+ * linger is still holding the pull open so a quick re-attach costs nothing; between the two the pull is
82
+ * alive. This fires when it is not.
83
+ */
84
+ onStopped?: () => void;
76
85
  /**
77
86
  * A media start was abandoned unacknowledged before anything was delivered, so this session is not being
78
87
  * heard. The owner is asked for a replacement and calls {@link SharedLiveSource.rewarm} once it has one.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Station topology and per-station channel addressing, from the device records alone.
3
+ *
4
+ * Both sides of the P2P wire key on a device's channel within its station: a media start and every
5
+ * per-channel command address it, and an inbound frame is attributed to the device on its channel. Resolving
6
+ * it once, here, keeps the two sides from ever disagreeing about which device a channel means.
7
+ *
8
+ * @module transport/p2p/station-channels
9
+ * @internal
10
+ */
11
+ import type { EufyDevice } from "../../core/types.js";
12
+ /**
13
+ * The station a device's traffic belongs to, from its cloud record and its own serial.
14
+ *
15
+ * `parent_sn` carries the parent on a HomeBase-attached device. `station_sn` is frequently absent there —
16
+ * empty on every attached sensor of a T8010 — and serves only as a fallback. An empty string states no
17
+ * station.
18
+ *
19
+ * A device naming no parent answers its own serial, so every device has a station.
20
+ */
21
+ export declare function resolvedStationSn(raw: Record<string, unknown>, sn: string): string;
22
+ /**
23
+ * The parent station a device's traffic belongs to — the session key the router opens and the station the
24
+ * frame resolver attributes by.
25
+ *
26
+ * `parent_sn` on the cloud record is the field that is actually populated for a HomeBase-attached device, so
27
+ * it wins; `stationSn` is frequently absent (observed empty on every attached sensor of a T8010), so keying on
28
+ * it alone silently resolves an attached device to ITSELF and no frame ever matches. Answering the device's
29
+ * OWN serial is what "stands alone" means.
30
+ */
31
+ export declare function stationOf(dev: EufyDevice): string;
32
+ /**
33
+ * A device's channel within its station, or why it has none. `shared` keeps the channel it `claimed`, so a
34
+ * frame arriving on it is known to belong to one of the claimants rather than to the station.
35
+ */
36
+ export type StationChannel = {
37
+ channel: number;
38
+ } | {
39
+ issue: "missing";
40
+ } | {
41
+ issue: "shared";
42
+ claimed: number;
43
+ };
44
+ /**
45
+ * The channel each device is addressed by within its station.
46
+ *
47
+ * A standalone device is its own station, addressed by the channel its record states, or 0. An attached
48
+ * device has a channel only when its record states one that no other device attached to the same station
49
+ * also states. A missing channel names no device, and a channel two attached devices both state cannot say
50
+ * which of them it means, so either resolves to an issue rather than to a channel: guessing would address
51
+ * another device, streaming its video or attributing its frames under this serial. Every attached device
52
+ * counts towards a clash, whatever its kind, and the answer does not depend on list order.
53
+ */
54
+ export declare function stationChannels(devices: readonly EufyDevice[]): Map<string, StationChannel>;
@@ -10,7 +10,13 @@ export declare class StoredImageCache {
10
10
  private activeDownloads;
11
11
  private generation;
12
12
  constructor(downloader: (url: string, deviceKey: string) => Promise<Buffer>, logger: Logger, clock?: () => number, isLifecycleError?: (error: unknown) => boolean);
13
- /** Observe a normalized thumbnail URL and start acquisition eagerly. */
13
+ /**
14
+ * Observe a normalized thumbnail URL and start acquisition eagerly.
15
+ *
16
+ * A URL already inside this device's window of recent attempts is ignored, so one event arriving as
17
+ * several pushes downloads one thumbnail. The window is a `Set`, which iterates in insertion order,
18
+ * so the entry evicted once it is full is the oldest attempt.
19
+ */
14
20
  observe(deviceKey: string, url: string): void;
15
21
  /** Return retained bytes without starting or awaiting network work. */
16
22
  snapshotStored(deviceKey: string): Promise<Buffer>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mega-yfue/eufy-sdk",
3
- "version": "0.2.0-beta.9",
3
+ "version": "0.3.0-beta.0",
4
4
  "description": "One typed TypeScript client for the Anker eufy v6 cloud — capability-driven devices, realtime events over P2P/MQTT/push, and live media",
5
5
  "license": "Apache-2.0",
6
6
  "author": "mega-yfue",
@@ -73,14 +73,14 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "jpeg-js": "^0.4.4",
76
- "mqtt": "^5.15.2",
76
+ "mqtt": "^5.16.0",
77
77
  "protobufjs": "^8.8.0"
78
78
  },
79
79
  "devDependencies": {
80
- "@types/node": "^26.5.0",
80
+ "@types/node": "^26.6.2",
81
81
  "esbuild": "^0.28.2",
82
- "prettier": "^3.9.6",
82
+ "prettier": "^3.9.9",
83
83
  "typescript": "^7.0.2",
84
- "vitest": "^5.0.0"
84
+ "vitest": "^5.0.1"
85
85
  }
86
86
  }