@haskou/value-objects 2.10.1 β 2.11.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.
- package/README.md +70 -252
- package/package.json +32 -4
package/README.md
CHANGED
|
@@ -3,292 +3,110 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@haskou/value-objects)
|
|
4
4
|
[](LICENSE.txt)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
## Documentation
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
and validated **Value Objects**. Perfect for applications that require **Domain-Driven Design (DDD)** and **type safety**.
|
|
8
|
+
Full documentation is available at **https://haskou.github.io/value-objects/**.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
The documentation includes installation, quick start, examples, error handling, serialization notes, and one reference page per exported class.
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
npm install @haskou/value-objects
|
|
15
|
-
```
|
|
12
|
+
Reusable agent instructions and engineering skills are available at **https://github.com/haskou/ddd-engineer-skills**.
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
import { Email, PositiveNumber, Color, Hour } from '@haskou/value-objects';
|
|
14
|
+
# Value Objects
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
const email = new Email('user@example.com'); // Valid
|
|
22
|
-
const price = new PositiveNumber(29.99); // Valid
|
|
23
|
-
const color = new Color('#FF0000'); // Valid
|
|
24
|
-
const hour = new Hour('09:30'); // Valid
|
|
16
|
+
A TypeScript library for validated, immutable primitive wrappers and small utility value objects.
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
const badEmail = new Email('not-an-email'); // Error!
|
|
28
|
-
const badPrice = new PositiveNumber(-5); // Error!
|
|
29
|
-
```
|
|
18
|
+
It provides ready-to-use objects for strings, numbers, identifiers, dates, coordinates, hashes, media, collections, and cryptographic payload helpers. Validation happens when an object is created, so code receiving one of these objects can rely on its shape.
|
|
30
19
|
|
|
31
|
-
##
|
|
20
|
+
## Installation
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function createUser(email: string, age: number): void {
|
|
36
|
-
// Are they valid? We don't know until runtime
|
|
37
|
-
if (!email.includes('@')) throw new Error('Invalid email');
|
|
38
|
-
if (age <= 0) throw new Error('Invalid age');
|
|
39
|
-
// Creation stuff
|
|
40
|
-
}
|
|
22
|
+
```bash
|
|
23
|
+
npm install @haskou/value-objects
|
|
41
24
|
```
|
|
42
25
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function createUser(email: Email, age: PositiveNumber): void {
|
|
46
|
-
// If we get here, values ARE valid
|
|
47
|
-
// Creation stuff
|
|
48
|
-
}
|
|
26
|
+
```bash
|
|
27
|
+
yarn add @haskou/value-objects
|
|
49
28
|
```
|
|
50
29
|
|
|
51
|
-
|
|
52
|
-
since it implements Null Object pattern (see technical documentation).
|
|
53
|
-
Also value-objects can be modified easily keeping the same logic along
|
|
54
|
-
your application.
|
|
55
|
-
|
|
56
|
-
## π¦ Available Value Objects
|
|
57
|
-
|
|
58
|
-
### π€ Basic
|
|
59
|
-
- **`StringValueObject`** - Strings with length validation
|
|
60
|
-
- **`Password`** - Password strings with length and complexity validation
|
|
61
|
-
- **`NumberValueObject`** - Numbers with math operations
|
|
62
|
-
- **`Integer`** - Whole numbers
|
|
63
|
-
- **`PositiveNumber`** - Positive numbers (> 0)
|
|
64
|
-
|
|
65
|
-
### π Identifiers
|
|
66
|
-
- **`ShortId`** - MongoDB ObjectId strings (24βcharacter hex)
|
|
67
|
-
- **`UUID`** - Universally Unique Identifier (v4)
|
|
68
|
-
|
|
69
|
-
### β¨ Specialized
|
|
70
|
-
- **`Email`** - Email addresses with automatic validation
|
|
71
|
-
- **`Color`** - Hex colors (#FF0000)
|
|
72
|
-
|
|
73
|
-
### π Time
|
|
74
|
-
- **`Hour`** - Time in 24h format (09:30)
|
|
75
|
-
- **`Year`** - Years with leap year calculations
|
|
76
|
-
- **`CalendarDay`** - Calendar days
|
|
77
|
-
- **`Day`** - Days of month (1-31)
|
|
78
|
-
- **`DayOfWeek`** - Days of the week
|
|
79
|
-
- **`Month`** - Months (1-12)
|
|
80
|
-
- **`MonthOfYear`** - Month/year combinations
|
|
81
|
-
- **`Timestamp`** - Timestamps with operations
|
|
82
|
-
- **`TimestampInterval`** - Time intervals
|
|
83
|
-
- **`Duration`** - Duration in milliseconds
|
|
84
|
-
|
|
85
|
-
### π Coordinates
|
|
86
|
-
- **`Latitude`** - Latitude (-90 to 90)
|
|
87
|
-
- **`Longitude`** - Longitude (-180 to 180)
|
|
88
|
-
- **`Coordinates`** - Coordinate pairs
|
|
89
|
-
|
|
90
|
-
### π Cryptography
|
|
91
|
-
- **`KeyPair`** - Ed25519 key pair generation for signing and verification
|
|
92
|
-
- **`PrivateKey`** - Ed25519 private key (PEM format) for signing; also accepted by asymmetric payload decryption for data addressed to the matching key pair
|
|
93
|
-
- **`PublicKey`** - Ed25519 public key (PEM format) for signature verification; also accepted by asymmetric payload encryption to address data to the matching key pair
|
|
94
|
-
- **`Signature`** - Base64-encoded Ed25519 digital signature
|
|
95
|
-
- **`EncryptedPayload`** - Base container for dot-separated encrypted payload formats
|
|
96
|
-
- **`AsymmetricEncryptedPayload`** - Payload encrypted for an Ed25519/X25519 recipient key pair
|
|
97
|
-
- **`SymmetricEncryptedPayload`** - Payload encrypted with a symmetric AES-256-GCM key
|
|
98
|
-
- **`SymmetricKey`** - 32-byte AES-256-GCM key, randomly generated or deterministically derived from a `Password` and salt with scrypt
|
|
99
|
-
- **`EncryptedPrivateKey`** - Password-protected private key encryption using scrypt + AES-256-GCM
|
|
100
|
-
- **`EncryptedKeyPair`** - Key pair with encrypted private key
|
|
101
|
-
|
|
102
|
-
Technical notes:
|
|
103
|
-
|
|
104
|
-
- Asymmetric payload encryption is a classical hybrid scheme. It generates an ephemeral
|
|
105
|
-
X25519 key pair, converts the recipient Ed25519 key material to Montgomery
|
|
106
|
-
form for X25519 key agreement, derives a shared secret, then derives a
|
|
107
|
-
256-bit AES key with HKDF-SHA256 and encrypts the payload with AES-256-GCM.
|
|
108
|
-
- The current asymmetric payload format is
|
|
109
|
-
`v2.x25519-hkdf-sha256-aes-256-gcm.ephemeralPublicKey.iv.cipherText.tag`,
|
|
110
|
-
with Base64-encoded key, IV, ciphertext, and tag fields. The previous
|
|
111
|
-
`ephemeralPublicKey.iv.cipherText.tag` format still decrypts for backward
|
|
112
|
-
compatibility.
|
|
113
|
-
- The v2 asymmetric header is authenticated as AES-GCM AAD. HKDF uses
|
|
114
|
-
`ephemeralPublicKey || recipientPublicKey` as salt and
|
|
115
|
-
`@haskou/value-objects/asymmetric-payload/v2` as domain-separated info.
|
|
116
|
-
- Symmetric payload encryption uses a 32-byte AES-256-GCM key directly. The
|
|
117
|
-
payload format is `v1.aes-256-gcm.iv.cipherText.tag`, with Base64-encoded
|
|
118
|
-
IV, ciphertext, and tag fields.
|
|
119
|
-
- `SymmetricKey.generate()` creates a random 256-bit key.
|
|
120
|
-
`SymmetricKey.fromPasswordUsingOwasp()` derives a 256-bit key with the OWASP
|
|
121
|
-
scrypt profile used by this package for new password-derived keys.
|
|
122
|
-
`SymmetricKey.fromPassword()` keeps its original defaults for backward
|
|
123
|
-
compatibility with existing encrypted data.
|
|
124
|
-
- A 32-byte key provides the AES-256 key size. Real security depends on using
|
|
125
|
-
either a high-entropy random key or a strong password with a unique,
|
|
126
|
-
non-empty salt. AES-GCM also requires that IVs do not repeat for the same key;
|
|
127
|
-
the library generates a random 96-bit IV for each encryption.
|
|
128
|
-
- Asymmetric payload encryption is intended for small payloads and is currently
|
|
129
|
-
capped at 1 MiB before encryption. Symmetric payload encryption is capped at
|
|
130
|
-
8 MiB before encryption.
|
|
131
|
-
- New encrypted private keys use `v3.scrypt.N16384.r8.p5` with a 16-byte salt,
|
|
132
|
-
then AES-256-GCM with a 12-byte IV and 16-byte authentication tag. The older
|
|
133
|
-
v2 scrypt profile and legacy PBKDF2 format still decrypt, and
|
|
134
|
-
`needsReEncryption()` marks them for upgrade. The v3 header is authenticated
|
|
135
|
-
as AES-GCM AAD.
|
|
136
|
-
- This is not a post-quantum cryptography scheme. Ed25519 and X25519 are
|
|
137
|
-
classical elliptic-curve primitives, so neither signatures nor payload key
|
|
138
|
-
agreement are post-quantum secure.
|
|
139
|
-
- Payload encryption uses fresh ephemeral key material and a random IV per
|
|
140
|
-
encryption, but captured payloads can still be decrypted later if the
|
|
141
|
-
recipient private key is compromised. Treat it as recipient-addressed
|
|
142
|
-
encryption with ciphertext integrity, not as sender-authenticated encryption
|
|
143
|
-
or a forward-secret transport protocol.
|
|
144
|
-
- The payload encryption format is library-specific, not HPKE, and has not been
|
|
145
|
-
independently audited as a protocol.
|
|
146
|
-
|
|
147
|
-
### π Media
|
|
148
|
-
- **`Media`** - Binary/string content with Buffer, size, and Base64 helpers
|
|
149
|
-
|
|
150
|
-
### #οΈ Hashes
|
|
151
|
-
- **`MD5Hash`** - MD5 hashes with helpers
|
|
152
|
-
- **`SHA256Hash`** - SHAβ256 hashes with helpers
|
|
153
|
-
- **`SHA512Hash`** - SHAβ512 hashes with helpers
|
|
154
|
-
|
|
155
|
-
### π Other
|
|
156
|
-
- **`Enum`** - Base class for typed enumerations
|
|
157
|
-
- **`UniqueObjectArray`** - Iterable collection that keeps comparable items unique
|
|
158
|
-
|
|
159
|
-
## π‘ Basic Examples
|
|
30
|
+
## Quick start
|
|
160
31
|
|
|
161
32
|
```typescript
|
|
162
|
-
|
|
163
|
-
const name = new StringValueObject('John Doe');
|
|
164
|
-
const code = new StringValueObject('ABC', 3); // max 3 characters
|
|
165
|
-
|
|
166
|
-
// Numbers with operations
|
|
167
|
-
const price = new NumberValueObject(29.99);
|
|
168
|
-
const discount = new NumberValueObject(5.00);
|
|
169
|
-
const total = price.subtract(discount); // 24.99
|
|
33
|
+
import { Color, Email, Hour, PositiveNumber } from '@haskou/value-objects';
|
|
170
34
|
|
|
171
|
-
// Emails
|
|
172
35
|
const email = new Email('user@example.com');
|
|
173
|
-
|
|
36
|
+
const price = new PositiveNumber(29.99);
|
|
37
|
+
const color = new Color('#FF0000');
|
|
38
|
+
const hour = new Hour('09:30');
|
|
174
39
|
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
40
|
+
console.log(email.valueOf()); // 'user@example.com'
|
|
41
|
+
console.log(price.valueOf()); // 29.99
|
|
42
|
+
console.log(color.toString()); // '#FF0000'
|
|
43
|
+
console.log(hour.addMinutes(45).toString()); // '10:15'
|
|
44
|
+
```
|
|
178
45
|
|
|
179
|
-
|
|
180
|
-
const latitude = new Latitude(40.7128); // New York
|
|
181
|
-
const longitude = new Longitude(-74.0060);
|
|
182
|
-
const coords = new Coordinates(latitude, longitude);
|
|
46
|
+
Invalid values throw specific errors.
|
|
183
47
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const md5 = MD5Hash.from('hello'); // 5d41402abc4b2a76b9719d911017c592
|
|
195
|
-
const sha256 = SHA256Hash.from('hello'); // 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
|
|
196
|
-
const sha512 = SHA512Hash.from('hello'); // 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043
|
|
197
|
-
console.log(md5.toBase64());
|
|
198
|
-
|
|
199
|
-
// Crypto
|
|
200
|
-
const keyPair = await KeyPair.generate(); // Ed25519 key pair
|
|
201
|
-
const signature = keyPair.sign('hello world'); // Sign a message
|
|
202
|
-
keyPair.isValidSignature('hello world', signature); // true
|
|
203
|
-
|
|
204
|
-
// Encrypted key pairs
|
|
205
|
-
const cryptoPassword = new Password('Secure-password-123!');
|
|
206
|
-
const encrypted = await keyPair.encryptKeyPair(cryptoPassword);
|
|
207
|
-
const sig = await encrypted.sign('message', cryptoPassword);
|
|
208
|
-
encrypted.isValidSignature('message', sig); // true
|
|
209
|
-
|
|
210
|
-
// Symmetric payload encryption
|
|
211
|
-
const symmetricKey = SymmetricKey.generate();
|
|
212
|
-
const symmetricPayload = symmetricKey.encrypt('secret data');
|
|
213
|
-
const plaintext = symmetricKey.decrypt(symmetricPayload);
|
|
214
|
-
console.log(plaintext.toString()); // 'secret data'
|
|
215
|
-
|
|
216
|
-
// Deterministic key derivation from a password and explicit salt
|
|
217
|
-
const derivedKey = await SymmetricKey.fromPasswordUsingOwasp(
|
|
218
|
-
cryptoPassword,
|
|
219
|
-
{ salt: 'stable-application-salt' },
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
// Media
|
|
223
|
-
const media = new Media('hello world');
|
|
224
|
-
console.log(media.getSize()); // 11
|
|
225
|
-
console.log(media.getBase64()); // 'aGVsbG8gd29ybGQ='
|
|
226
|
-
console.log(media.getBuffer()); // <Buffer 68 65 6c 6c 6f ...>
|
|
227
|
-
|
|
228
|
-
// Unique collections
|
|
229
|
-
const weekdays = UniqueObjectArray.fromArray([
|
|
230
|
-
DayOfWeek.MONDAY,
|
|
231
|
-
DayOfWeek.TUESDAY,
|
|
232
|
-
DayOfWeek.MONDAY,
|
|
233
|
-
]);
|
|
234
|
-
console.log(weekdays.length()); // 2
|
|
235
|
-
console.log(weekdays.toArray().map((day) => day.toString())); // ['monday', 'tuesday']
|
|
48
|
+
```typescript
|
|
49
|
+
import { Email, InvalidEmailError } from '@haskou/value-objects';
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
new Email('not-an-email');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof InvalidEmailError) {
|
|
55
|
+
console.error('Invalid email');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
236
58
|
```
|
|
237
59
|
|
|
238
|
-
##
|
|
239
|
-
|
|
240
|
-
Need more details? Check the complete documentation:
|
|
60
|
+
## Available categories
|
|
241
61
|
|
|
242
|
-
|
|
62
|
+
| Category | Examples |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| Base | `ValueObject`, `NullObject`, `Enum` |
|
|
65
|
+
| Strings | `StringValueObject`, `Password`, `Email`, `Color` |
|
|
66
|
+
| Numbers | `NumberValueObject`, `Integer`, `PositiveNumber` |
|
|
67
|
+
| IDs | `ShortId`, `UUID` |
|
|
68
|
+
| Time | `Timestamp`, `CalendarDay`, `Hour`, `Duration`, `MonthOfYear` |
|
|
69
|
+
| Coordinates | `Latitude`, `Longitude`, `Coordinates` |
|
|
70
|
+
| Hashes | `MD5Hash`, `SHA256Hash`, `SHA512Hash` |
|
|
71
|
+
| Media | `Media` |
|
|
72
|
+
| Collections | `UniqueObjectArray` |
|
|
73
|
+
| Crypto helpers | `KeyPair`, `PrivateKey`, `PublicKey`, `SymmetricKey`, encrypted payload objects |
|
|
243
74
|
|
|
244
|
-
|
|
245
|
-
- β
Complete API for all Value Objects
|
|
246
|
-
- β
Advanced usage examples
|
|
247
|
-
- β
Error handling
|
|
248
|
-
- β
Design principles (immutability, null safety, etc.)
|
|
249
|
-
- β
Composition and extensibility patterns
|
|
75
|
+
See the complete API reference in the documentation: https://haskou.github.io/value-objects/reference/
|
|
250
76
|
|
|
251
|
-
##
|
|
77
|
+
## Documentation development
|
|
252
78
|
|
|
253
79
|
```bash
|
|
254
|
-
#
|
|
255
|
-
|
|
80
|
+
# Start local docs server
|
|
81
|
+
yarn docs:dev
|
|
256
82
|
|
|
257
|
-
#
|
|
258
|
-
|
|
83
|
+
# Build static documentation
|
|
84
|
+
yarn docs:build
|
|
259
85
|
|
|
260
|
-
#
|
|
261
|
-
|
|
86
|
+
# Preview the built site
|
|
87
|
+
yarn docs:preview
|
|
262
88
|
```
|
|
263
89
|
|
|
264
|
-
##
|
|
265
|
-
|
|
266
|
-
Publishing is handled by CI when a pull request is merged into the default
|
|
267
|
-
branch (`master`, or `main` after a branch rename). Use these branch prefixes
|
|
268
|
-
to choose the npm version bump:
|
|
90
|
+
## Package development
|
|
269
91
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
to npm. Publishing uses npm Trusted Publishing from the `ci.yml` workflow.
|
|
276
|
-
After a successful publish, CI commits the release version to the default
|
|
277
|
-
branch, creates the matching `vX.Y.Z` Git tag, and publishes a compact GitHub
|
|
278
|
-
Release with the PR title, npm package link, release branch, and PR notes.
|
|
92
|
+
```bash
|
|
93
|
+
yarn install
|
|
94
|
+
yarn test
|
|
95
|
+
yarn build
|
|
96
|
+
```
|
|
279
97
|
|
|
280
|
-
##
|
|
98
|
+
## Release branches
|
|
281
99
|
|
|
282
|
-
|
|
283
|
-
2. Create a branch: `git checkout -b feat/my-feature`
|
|
284
|
-
3. Make your changes and add tests
|
|
285
|
-
4. Run tests: `npm test`
|
|
286
|
-
5. Submit a pull request
|
|
100
|
+
Publishing is handled by CI when a pull request is merged into the default branch.
|
|
287
101
|
|
|
288
|
-
|
|
102
|
+
| Branch prefix | npm bump |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `fix/*` | Patch |
|
|
105
|
+
| `feat/*` | Minor |
|
|
106
|
+
| `break/*` | Major |
|
|
289
107
|
|
|
290
|
-
|
|
108
|
+
Branches without one of these prefixes still run CI, but they do not publish to npm.
|
|
291
109
|
|
|
292
|
-
|
|
110
|
+
## License
|
|
293
111
|
|
|
294
|
-
|
|
112
|
+
MIT. See [LICENSE.txt](LICENSE.txt).
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haskou/value-objects",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
|
+
"description": "TypeScript value objects for validated, immutable primitive values and small domain-safe data structures.",
|
|
4
5
|
"main": "dist/index.js",
|
|
5
6
|
"types": "dist/index.d.ts",
|
|
6
7
|
"exports": {
|
|
@@ -19,6 +20,28 @@
|
|
|
19
20
|
"engines": {
|
|
20
21
|
"node": ">=20.19.0"
|
|
21
22
|
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"value-object",
|
|
25
|
+
"value-objects",
|
|
26
|
+
"typescript",
|
|
27
|
+
"validation",
|
|
28
|
+
"validated-types",
|
|
29
|
+
"immutable",
|
|
30
|
+
"immutability",
|
|
31
|
+
"type-safety",
|
|
32
|
+
"domain-primitives",
|
|
33
|
+
"primitive-obsession",
|
|
34
|
+
"ddd",
|
|
35
|
+
"domain-driven-design",
|
|
36
|
+
"email-validation",
|
|
37
|
+
"uuid",
|
|
38
|
+
"hash",
|
|
39
|
+
"crypto",
|
|
40
|
+
"aes-256-gcm",
|
|
41
|
+
"ed25519",
|
|
42
|
+
"x25519",
|
|
43
|
+
"scrypt"
|
|
44
|
+
],
|
|
22
45
|
"publishConfig": {
|
|
23
46
|
"access": "public"
|
|
24
47
|
},
|
|
@@ -29,14 +52,17 @@
|
|
|
29
52
|
"bugs": {
|
|
30
53
|
"url": "https://github.com/haskou/value-objects/issues"
|
|
31
54
|
},
|
|
32
|
-
"homepage": "https://github.
|
|
55
|
+
"homepage": "https://haskou.github.io/value-objects/",
|
|
33
56
|
"scripts": {
|
|
34
57
|
"lint": "eslint ./src --ext .ts",
|
|
35
58
|
"lint:fix": "eslint ./src --ext .ts --fix",
|
|
36
59
|
"test": "yarn lint && yarn test:unit",
|
|
37
60
|
"test:unit": "jest --runInBand",
|
|
38
61
|
"test:coverage": "NODE_ENV=test jest --collect-coverage",
|
|
39
|
-
"build": "rm -rf dist/ && tsc"
|
|
62
|
+
"build": "rm -rf dist/ && tsc",
|
|
63
|
+
"docs:dev": "vitepress dev docs",
|
|
64
|
+
"docs:build": "vitepress build docs",
|
|
65
|
+
"docs:preview": "vitepress build docs && vitepress preview docs"
|
|
40
66
|
},
|
|
41
67
|
"dependencies": {
|
|
42
68
|
"@noble/ciphers": "^2.2.0",
|
|
@@ -60,6 +86,8 @@
|
|
|
60
86
|
"jest-extended": "^7.0.0",
|
|
61
87
|
"prettier": "^3.8.4",
|
|
62
88
|
"ts-jest": "^29.4.11",
|
|
63
|
-
"typescript": "^6.0.3"
|
|
89
|
+
"typescript": "^6.0.3",
|
|
90
|
+
"vitepress": "^1.6.4",
|
|
91
|
+
"vue": "^3.5.22"
|
|
64
92
|
}
|
|
65
93
|
}
|