@fluojs/serialization 1.0.4 → 2.0.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.ko.md +34 -4
- package/README.md +34 -4
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +10 -5
- package/package.json +5 -5
package/README.ko.md
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
<p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
|
|
4
4
|
|
|
5
|
+
Node.js 지원 범위는 `>=24.0.0 <27`입니다. 업그레이드 절차는 [Node.js 지원 및 마이그레이션](../../docs/reference/node-support.ko.md)을 참조하세요.
|
|
6
|
+
|
|
5
7
|
fluo를 위한 클래스 기반 응답 직렬화 및 데코레이터 인지형 재귀 출력 가공 엔진입니다.
|
|
6
8
|
|
|
7
9
|
## 목차
|
|
8
10
|
|
|
9
11
|
- [설치](#설치)
|
|
10
12
|
- [사용 시점](#사용-시점)
|
|
13
|
+
- [데코레이터 메타데이터 사전 로드](#데코레이터-메타데이터-사전-로드)
|
|
11
14
|
- [빠른 시작](#빠른-시작)
|
|
12
15
|
- [주요 패턴](#주요-패턴)
|
|
13
16
|
- [공개 API 개요](#공개-api-개요)
|
|
@@ -27,6 +30,18 @@ pnpm add @fluojs/serialization
|
|
|
27
30
|
- response data가 serialization 중 lightweight synchronous transform을 거쳐야 할 때
|
|
28
31
|
- HTTP interceptor가 같은 serialization rule을 자동으로 적용하게 하고 싶을 때
|
|
29
32
|
|
|
33
|
+
## 데코레이터 메타데이터 사전 로드
|
|
34
|
+
|
|
35
|
+
`@fluojs/serialization`은 import side effect로 `Symbol.metadata`를 설치하지 않습니다. 대상 runtime이 이를 기본 제공하지 않는다면 `@Expose()`, `@Exclude()`, `@Transform()`으로 decorate한 클래스를 평가하는 module을 import하기 전에 설치하세요.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// preload.ts — 이 파일을 애플리케이션 entrypoint로 설정합니다.
|
|
39
|
+
import { ensureMetadataSymbol } from '@fluojs/core';
|
|
40
|
+
|
|
41
|
+
ensureMetadataSymbol();
|
|
42
|
+
await import('./bootstrap.js');
|
|
43
|
+
```
|
|
44
|
+
|
|
30
45
|
## 빠른 시작
|
|
31
46
|
|
|
32
47
|
```ts
|
|
@@ -70,6 +85,8 @@ class SecureDto {
|
|
|
70
85
|
}
|
|
71
86
|
```
|
|
72
87
|
|
|
88
|
+
`ExposeClassOptions`는 `Expose(...)`가 받는 class-level option을 표현하는 export 타입입니다. DTO가 field-level `@Expose()` metadata가 있는 field만 내보내야 할 때 `excludeExtraneous: true`를 사용하세요.
|
|
89
|
+
|
|
73
90
|
### 값 변환
|
|
74
91
|
|
|
75
92
|
```ts
|
|
@@ -82,11 +99,12 @@ class ProductDto {
|
|
|
82
99
|
```
|
|
83
100
|
|
|
84
101
|
같은 필드가 base class와 derived class 모두에서 decorate되면 transform은 base에서 derived 순서로 실행됩니다.
|
|
102
|
+
`TransformFunction`은 동기식 `(value: unknown) => unknown` callback입니다. 현재 field value만 전달받으므로 async 작업이나 DTO, property metadata, serialization context 접근이 아니라 value-only transform에 사용하세요.
|
|
85
103
|
|
|
86
104
|
### HTTP 인터셉터와 함께 사용
|
|
87
105
|
|
|
88
106
|
```ts
|
|
89
|
-
import { Controller, Get, UseInterceptors } from '@fluojs/http';
|
|
107
|
+
import { Controller, Get, type RequestContext, UseInterceptors } from '@fluojs/http';
|
|
90
108
|
import { SerializerInterceptor } from '@fluojs/serialization';
|
|
91
109
|
|
|
92
110
|
@Controller('/users')
|
|
@@ -96,10 +114,21 @@ class UsersController {
|
|
|
96
114
|
findAll() {
|
|
97
115
|
return [new UserEntity()];
|
|
98
116
|
}
|
|
117
|
+
|
|
118
|
+
@Get('/export.csv')
|
|
119
|
+
async exportCsv(_input: undefined, context: RequestContext) {
|
|
120
|
+
context.response.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
121
|
+
await context.response.send('id,username\n1,fluo');
|
|
122
|
+
}
|
|
99
123
|
}
|
|
100
124
|
```
|
|
101
125
|
|
|
102
|
-
|
|
126
|
+
두 route는 서로 다른 응답 소유자를 사용합니다.
|
|
127
|
+
|
|
128
|
+
- **Framework-managed response**: `findAll()`은 `RequestContext.response`가 아직 commit되지 않은 상태에서 반환합니다. `SerializerInterceptor`가 반환된 DTO를 직렬화한 뒤 runtime response writer가 결과를 commit합니다.
|
|
129
|
+
- **Handler-owned response**: `exportCsv()`는 최종 payload를 `RequestContext.response.send(...)`로 직접 씁니다. `send(...)`, `redirect(...)`, 또는 수동 streaming helper가 response를 commit하면 `SerializerInterceptor`는 `serialize(...)`를 건너뛰고 `next.handle()`에서 받은 값을 그대로 반환합니다. 이 보장은 `SerializerInterceptor`에만 해당하며, 다른 interceptor는 chain 결과를 계속 변환할 수 있습니다. 이와 별개로 dispatcher는 commit된 response를 확인하고 두 번째 success-response write를 건너뜁니다.
|
|
130
|
+
|
|
131
|
+
직접 쓰는 payload는 최종 결과로 취급하세요. 필요한 field filtering이나 encoding을 commit 전에 적용해야 합니다. Handler/runtime response ownership이 commit된 뒤에는 serialization이 응답을 후처리할 수 없습니다.
|
|
103
132
|
|
|
104
133
|
### 순환 참조 처리
|
|
105
134
|
|
|
@@ -108,6 +137,7 @@ fluo의 직렬화 엔진은 활성 순환 참조를 자동으로 감지하고 `u
|
|
|
108
137
|
### 상속된 데코레이터 계약
|
|
109
138
|
|
|
110
139
|
기반 클래스에 선언한 직렬화 메타데이터는 파생 DTO에도 상속됩니다. 공통 필드에 적용한 `@Expose()`, `@Exclude()`, `@Transform()` 규칙은 서브클래스 인스턴스를 직렬화할 때도 그대로 반영됩니다.
|
|
140
|
+
파생 클래스의 데코레이터 갱신은 해당 클래스만 소유하므로 field나 class option을 override해도 base DTO나 sibling DTO의 이후 직렬화는 바뀌지 않습니다.
|
|
111
141
|
|
|
112
142
|
Class-level `excludeExtraneous`도 일반 상속 규칙을 따릅니다. 파생 클래스에 option 없는 `@Expose()`를 붙여도 가장 가까운 상속 설정이 유지되므로, expose-only 기반 DTO는 subclass에서도 expose-only 상태를 유지합니다. 일반 enumerable field를 다시 포함하려는 의도가 있을 때만 파생 클래스에 `@Expose({ excludeExtraneous: false })`를 명시하세요. 이 경우에도 상속된 field-level `@Exclude()` metadata는 계속 적용됩니다.
|
|
113
143
|
|
|
@@ -125,8 +155,8 @@ Decorated metadata가 없는 class instance도 재귀적으로 순회하므로,
|
|
|
125
155
|
|
|
126
156
|
- **데코레이터**: `Expose`, `Exclude`, `Transform`
|
|
127
157
|
- **엔진**: `serialize(value)`는 class instance, 배열, plain object, mixed graph를 재귀적으로 순회하며, 직접 transform하지 않은 opaque built-in 및 non-JSON leaf 값은 보존합니다.
|
|
128
|
-
- **HTTP 통합**: `SerializerInterceptor`는 아직 commit되지 않은 handler 결과를
|
|
129
|
-
- **타입**: `TransformFunction`은 `Transform(...)`에 전달하는 callback 타입으로
|
|
158
|
+
- **HTTP 통합**: `SerializerInterceptor`는 아직 commit되지 않은 handler 결과를 직렬화합니다. Response가 commit된 뒤에는 `next.handle()`에서 받은 값을 그대로 반환하지만, 다른 interceptor는 chain 결과를 계속 변환할 수 있습니다.
|
|
159
|
+
- **타입**: `ExposeClassOptions`는 class-level `Expose(...)` option 타입으로 root entrypoint에서 export되며, `TransformFunction`은 `Transform(...)`에 전달하는 callback 타입으로 export됩니다.
|
|
130
160
|
|
|
131
161
|
`Expose`는 class와 field에 적용할 수 있습니다. `Exclude`와 `Transform`은 field에 적용합니다.
|
|
132
162
|
|
package/README.md
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
<p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
|
|
4
4
|
|
|
5
|
+
Node.js support is `>=24.0.0 <27`. See [Node.js support and migration](../../docs/reference/node-support.md) before upgrading.
|
|
6
|
+
|
|
5
7
|
Class-based response serialization and output shaping for fluo with decorator-aware recursive object walking.
|
|
6
8
|
|
|
7
9
|
## Table of Contents
|
|
8
10
|
|
|
9
11
|
- [Installation](#installation)
|
|
10
12
|
- [When to Use](#when-to-use)
|
|
13
|
+
- [Decorator Metadata Preload](#decorator-metadata-preload)
|
|
11
14
|
- [Quick Start](#quick-start)
|
|
12
15
|
- [Common Patterns](#common-patterns)
|
|
13
16
|
- [Public API Overview](#public-api-overview)
|
|
@@ -27,6 +30,18 @@ pnpm add @fluojs/serialization
|
|
|
27
30
|
- when response data needs lightweight synchronous transforms during serialization
|
|
28
31
|
- when you want an HTTP interceptor to apply the same serialization rules automatically
|
|
29
32
|
|
|
33
|
+
## Decorator Metadata Preload
|
|
34
|
+
|
|
35
|
+
`@fluojs/serialization` does not install `Symbol.metadata` as an import side effect. When your target runtime does not provide it natively, install it before importing any module that evaluates classes decorated with `@Expose()`, `@Exclude()`, or `@Transform()`:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// preload.ts — configure this as the application entrypoint
|
|
39
|
+
import { ensureMetadataSymbol } from '@fluojs/core';
|
|
40
|
+
|
|
41
|
+
ensureMetadataSymbol();
|
|
42
|
+
await import('./bootstrap.js');
|
|
43
|
+
```
|
|
44
|
+
|
|
30
45
|
## Quick Start
|
|
31
46
|
|
|
32
47
|
```ts
|
|
@@ -70,6 +85,8 @@ class SecureDto {
|
|
|
70
85
|
}
|
|
71
86
|
```
|
|
72
87
|
|
|
88
|
+
`ExposeClassOptions` is the exported class-level options type accepted by `Expose(...)`. Use `excludeExtraneous: true` when a DTO should emit only fields with field-level `@Expose()` metadata.
|
|
89
|
+
|
|
73
90
|
### Value transforms
|
|
74
91
|
|
|
75
92
|
```ts
|
|
@@ -82,11 +99,12 @@ class ProductDto {
|
|
|
82
99
|
```
|
|
83
100
|
|
|
84
101
|
When the same field is decorated in a base class and a derived class, transforms run in declaration order from base to derived.
|
|
102
|
+
`TransformFunction` is a synchronous `(value: unknown) => unknown` callback: it receives only the current field value, so use it for value-only transforms rather than async work or access to the DTO, property metadata, or serialization context.
|
|
85
103
|
|
|
86
104
|
### HTTP response shaping with an interceptor
|
|
87
105
|
|
|
88
106
|
```ts
|
|
89
|
-
import { Controller, Get, UseInterceptors } from '@fluojs/http';
|
|
107
|
+
import { Controller, Get, type RequestContext, UseInterceptors } from '@fluojs/http';
|
|
90
108
|
import { SerializerInterceptor } from '@fluojs/serialization';
|
|
91
109
|
|
|
92
110
|
@Controller('/users')
|
|
@@ -96,10 +114,21 @@ class UsersController {
|
|
|
96
114
|
findAll() {
|
|
97
115
|
return [new UserEntity()];
|
|
98
116
|
}
|
|
117
|
+
|
|
118
|
+
@Get('/export.csv')
|
|
119
|
+
async exportCsv(_input: undefined, context: RequestContext) {
|
|
120
|
+
context.response.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
121
|
+
await context.response.send('id,username\n1,fluo');
|
|
122
|
+
}
|
|
99
123
|
}
|
|
100
124
|
```
|
|
101
125
|
|
|
102
|
-
|
|
126
|
+
The two routes use different response owners:
|
|
127
|
+
|
|
128
|
+
- **Framework-managed response**: `findAll()` returns while `RequestContext.response` is still uncommitted. `SerializerInterceptor` serializes the returned DTOs, then the runtime response writer commits the result.
|
|
129
|
+
- **Handler-owned response**: `exportCsv()` writes the final payload through `RequestContext.response.send(...)`. Once `send(...)`, `redirect(...)`, or a manual streaming helper commits the response, `SerializerInterceptor` bypasses `serialize(...)` and returns the value it received from `next.handle()` unchanged. This guarantee is specific to `SerializerInterceptor`; other interceptors may still transform the chain result. Independently, the dispatcher sees the committed response and skips a second success-response write.
|
|
130
|
+
|
|
131
|
+
Treat a directly written payload as final: apply any required field filtering or encoding before the commit. Serialization cannot post-process a response after handler/runtime response ownership has been committed.
|
|
103
132
|
|
|
104
133
|
### Cycle-safe serialization
|
|
105
134
|
|
|
@@ -108,6 +137,7 @@ The serializer cuts active cyclic references safely instead of recursing forever
|
|
|
108
137
|
### Inherited decorator contracts
|
|
109
138
|
|
|
110
139
|
Serialization metadata declared on a base class is inherited by derived DTOs. `@Expose()`, `@Exclude()`, and `@Transform()` rules applied to shared base fields still take effect when you serialize subclass instances.
|
|
140
|
+
Derived decorators own their metadata updates, so overriding a field or class option never changes the later serialization of the base DTO or a sibling DTO.
|
|
111
141
|
|
|
112
142
|
Class-level `excludeExtraneous` also follows normal inheritance. A derived class with `@Expose()` and no options keeps the nearest inherited setting, so an expose-only base DTO remains expose-only in subclasses. Use `@Expose({ excludeExtraneous: false })` on the derived class only when you intentionally want to re-enable ordinary enumerable fields while still honoring inherited field-level `@Exclude()` metadata.
|
|
113
143
|
|
|
@@ -125,8 +155,8 @@ Undecorated class instances are still traversed recursively, so decorated nested
|
|
|
125
155
|
|
|
126
156
|
- **Decorators**: `Expose`, `Exclude`, `Transform`
|
|
127
157
|
- **Engine**: `serialize(value)` recursively walks class instances, arrays, plain objects, and mixed graphs while preserving opaque built-ins and non-JSON leaf values unless you transform them
|
|
128
|
-
- **HTTP integration**: `SerializerInterceptor` serializes uncommitted handler results
|
|
129
|
-
- **Types**: `
|
|
158
|
+
- **HTTP integration**: `SerializerInterceptor` serializes uncommitted handler results; after the response is committed, it returns the value it received from `next.handle()` unchanged, although other interceptors may still transform the chain result
|
|
159
|
+
- **Types**: `ExposeClassOptions` is exported from the root entrypoint for class-level `Expose(...)` options, and `TransformFunction` is exported for callbacks passed to `Transform(...)`
|
|
130
160
|
|
|
131
161
|
`Expose` can be applied to classes and fields. `Exclude` and `Transform` apply to fields.
|
|
132
162
|
|
package/dist/metadata.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAKxD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAClC;AA4DD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAE3G;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,OAAO,EACjB,WAAW,EAAE,mBAAmB,EAChC,MAAM,EAAE,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,KAAK,0BAA0B,GACtF,IAAI,CAGN;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,QAAQ,GAAG,yBAAyB,
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAKxD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAClC;AA4DD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAE3G;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,OAAO,EACjB,WAAW,EAAE,mBAAmB,EAChC,MAAM,EAAE,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,KAAK,0BAA0B,GACtF,IAAI,CAGN;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,QAAQ,GAAG,yBAAyB,CAU7F;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,WAAW,EAAE,QAAQ,GAAG,GAAG,CAAC,mBAAmB,EAAE,0BAA0B,CAAC,CA2BzH"}
|
package/dist/metadata.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getOwnConstructorRequestPipelineMetadataBag } from '@fluojs/core/request-pipeline';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Defines the transform function type.
|
|
@@ -23,7 +23,7 @@ function getStandardMetadataBag(metadata) {
|
|
|
23
23
|
function getFieldMetadataMap(metadata) {
|
|
24
24
|
const bag = getStandardMetadataBag(metadata);
|
|
25
25
|
const current = bag[standardSerializationFieldMetadataKey];
|
|
26
|
-
if (current) {
|
|
26
|
+
if (current && Object.hasOwn(bag, standardSerializationFieldMetadataKey)) {
|
|
27
27
|
return current;
|
|
28
28
|
}
|
|
29
29
|
const created = new Map();
|
|
@@ -33,7 +33,7 @@ function getFieldMetadataMap(metadata) {
|
|
|
33
33
|
function getClassMetadataObject(metadata) {
|
|
34
34
|
const bag = getStandardMetadataBag(metadata);
|
|
35
35
|
const current = bag[standardSerializationClassMetadataKey];
|
|
36
|
-
if (current) {
|
|
36
|
+
if (current && Object.hasOwn(bag, standardSerializationClassMetadataKey)) {
|
|
37
37
|
return current;
|
|
38
38
|
}
|
|
39
39
|
const created = {};
|
|
@@ -41,7 +41,7 @@ function getClassMetadataObject(metadata) {
|
|
|
41
41
|
return created;
|
|
42
42
|
}
|
|
43
43
|
function getOwnMetadataBagFromConstructor(constructor) {
|
|
44
|
-
return
|
|
44
|
+
return getOwnConstructorRequestPipelineMetadataBag(constructor);
|
|
45
45
|
}
|
|
46
46
|
function getConstructorMetadataBags(constructor) {
|
|
47
47
|
const bags = [];
|
|
@@ -87,7 +87,9 @@ export function updateFieldSerializationMetadata(metadata, propertyKey, update)
|
|
|
87
87
|
export function getClassSerializationOptions(constructor) {
|
|
88
88
|
const options = {};
|
|
89
89
|
for (const bag of getConstructorMetadataBags(constructor)) {
|
|
90
|
-
Object.
|
|
90
|
+
if (Object.hasOwn(bag, standardSerializationClassMetadataKey)) {
|
|
91
|
+
Object.assign(options, bag[standardSerializationClassMetadataKey]);
|
|
92
|
+
}
|
|
91
93
|
}
|
|
92
94
|
return options;
|
|
93
95
|
}
|
|
@@ -101,6 +103,9 @@ export function getClassSerializationOptions(constructor) {
|
|
|
101
103
|
export function getFieldSerializationMetadata(constructor) {
|
|
102
104
|
const merged = new Map();
|
|
103
105
|
for (const bag of getConstructorMetadataBags(constructor)) {
|
|
106
|
+
if (!Object.hasOwn(bag, standardSerializationFieldMetadataKey)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
104
109
|
const fieldMetadata = bag[standardSerializationFieldMetadataKey];
|
|
105
110
|
if (!fieldMetadata) {
|
|
106
111
|
continue;
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"output",
|
|
10
10
|
"transform"
|
|
11
11
|
],
|
|
12
|
-
"version": "
|
|
12
|
+
"version": "2.0.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"directory": "packages/serialization"
|
|
19
19
|
},
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
21
|
+
"node": ">=24.0.0 <27"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fluojs/core": "^
|
|
40
|
-
"@fluojs/http": "^
|
|
39
|
+
"@fluojs/core": "^2.0.0",
|
|
40
|
+
"@fluojs/http": "^3.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"vitest": "^
|
|
43
|
+
"vitest": "^4.1.11"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|