@fluojs/di 1.0.0-beta.5 → 1.0.0-beta.6
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 +8 -5
- package/README.md +8 -5
- package/dist/container.d.ts +13 -0
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +138 -21
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -76,6 +76,8 @@ const service = await container.resolve(UserService);
|
|
|
76
76
|
- **request**: `createRequestScope()`마다 새로 생성됩니다.
|
|
77
77
|
- **transient**: resolve할 때마다 새 인스턴스를 만듭니다.
|
|
78
78
|
|
|
79
|
+
dispose 중에는 루트 컨테이너가 먼저 살아 있는 request scope 자식을 정리한 뒤, 자식 dispose 중 하나 이상이 실패하더라도 루트가 소유한 singleton 정리를 계속 수행합니다. 자식/루트 dispose 실패가 여러 개 발생하면 `dispose()`는 모든 shutdown 실패를 확인할 수 있도록 `AggregateError`로 보고합니다.
|
|
80
|
+
|
|
79
81
|
### request scope 분리
|
|
80
82
|
|
|
81
83
|
```ts
|
|
@@ -91,7 +93,7 @@ provider 객체는 등록 시점에 검증됩니다. 모든 객체 provider는 n
|
|
|
91
93
|
|
|
92
94
|
컨테이너는 순환 의존성을 자동으로 감지하고 `CircularDependencyError`를 발생시켜 무한 루프를 방지합니다. 여기에는 직접 참조(A→A), 이중 노드(A→B→A), 깊은 순환(A→B→C→A)이 모두 포함됩니다.
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
선언 순서 때문에 아직 정의되지 않은 토큰을 참조해야 한다면 `forwardRef()`를 사용하세요. `forwardRef()`는 선언 순서 문제를 위해 토큰 조회를 지연할 뿐이며, 실제 생성자 순환을 해소하지는 않습니다. 그런 순환은 여전히 `CircularDependencyError`로 거부됩니다.
|
|
95
97
|
|
|
96
98
|
```typescript
|
|
97
99
|
import { forwardRef } from '@fluojs/di';
|
|
@@ -99,12 +101,13 @@ import { Inject } from '@fluojs/core';
|
|
|
99
101
|
|
|
100
102
|
@Inject(forwardRef(() => ServiceB))
|
|
101
103
|
class ServiceA {
|
|
102
|
-
constructor(private serviceB:
|
|
104
|
+
constructor(private readonly serviceB: ServiceB) {}
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
@Inject(forwardRef(() => ServiceA))
|
|
106
107
|
class ServiceB {
|
|
107
|
-
|
|
108
|
+
getStatus() {
|
|
109
|
+
return 'ready';
|
|
110
|
+
}
|
|
108
111
|
}
|
|
109
112
|
```
|
|
110
113
|
|
|
@@ -131,7 +134,7 @@ const service = await container.resolve(DataService);
|
|
|
131
134
|
## 문제 해결
|
|
132
135
|
|
|
133
136
|
### CircularDependencyError
|
|
134
|
-
의존성 그래프에서 순환이 감지될 때 발생합니다. 생성자 주입 항목을 확인하고
|
|
137
|
+
의존성 그래프에서 순환이 감지될 때 발생합니다. 생성자 주입 항목을 확인하고 공유 상태 추출, 중재자 도입, 수명 주기 경계 변경 등으로 순환을 제거하세요. `forwardRef()`는 선언 순서 문제를 위해 토큰 조회만 지연하며, 실제 생성자 순환을 끊지는 않습니다.
|
|
135
138
|
|
|
136
139
|
### 토큰을 찾을 수 없음 (Token Not Found)
|
|
137
140
|
필요한 모든 provider가 컨테이너에 등록되어 있는지 확인하세요. `createRequestScope()`를 사용하는 경우 자식 컨테이너는 부모의 토큰을 해석할 수 있지만, 그 반대는 불가능합니다.
|
package/README.md
CHANGED
|
@@ -75,6 +75,8 @@ fluo DI supports three main provider shapes:
|
|
|
75
75
|
- **Request**: Instance is created once per `createRequestScope()` call.
|
|
76
76
|
- **Transient**: A new instance is created every time it is resolved.
|
|
77
77
|
|
|
78
|
+
During disposal, the root container first tears down live request-scope children and then continues with root-owned singleton cleanup even if one or more child disposals fail. When multiple child/root disposals fail, `dispose()` reports an `AggregateError` so callers can inspect every shutdown failure without losing cleanup progress.
|
|
79
|
+
|
|
78
80
|
### Request Scoping
|
|
79
81
|
Isolated containers can be created to handle per-request state without polluting the root container.
|
|
80
82
|
|
|
@@ -91,7 +93,7 @@ Provider objects are validated at registration time: every object provider must
|
|
|
91
93
|
|
|
92
94
|
The container automatically detects circular dependencies and throws a `CircularDependencyError` to prevent infinite loops. This includes direct (A→A), two-node (A→B→A), and deep (A→B→C→A) cycles.
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
Use `forwardRef()` when a token is referenced before its declaration. It defers token lookup for declaration-order issues, but it does not make true constructor cycles resolvable; those cycles are still rejected with `CircularDependencyError`.
|
|
95
97
|
|
|
96
98
|
```typescript
|
|
97
99
|
import { forwardRef } from '@fluojs/di';
|
|
@@ -99,12 +101,13 @@ import { Inject } from '@fluojs/core';
|
|
|
99
101
|
|
|
100
102
|
@Inject(forwardRef(() => ServiceB))
|
|
101
103
|
class ServiceA {
|
|
102
|
-
constructor(private serviceB:
|
|
104
|
+
constructor(private readonly serviceB: ServiceB) {}
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
@Inject(forwardRef(() => ServiceA))
|
|
106
107
|
class ServiceB {
|
|
107
|
-
|
|
108
|
+
getStatus() {
|
|
109
|
+
return 'ready';
|
|
110
|
+
}
|
|
108
111
|
}
|
|
109
112
|
```
|
|
110
113
|
|
|
@@ -131,7 +134,7 @@ const service = await container.resolve(DataService);
|
|
|
131
134
|
## Troubleshooting
|
|
132
135
|
|
|
133
136
|
### CircularDependencyError
|
|
134
|
-
Thrown when the container detects a cycle in the dependency graph. Check your constructor injections and
|
|
137
|
+
Thrown when the container detects a cycle in the dependency graph. Check your constructor injections and remove the cycle by extracting shared state, introducing a mediator, or changing the lifetime boundary. `forwardRef()` only defers token lookup for declaration-order issues; it does not break true constructor cycles.
|
|
135
138
|
|
|
136
139
|
### Token Not Found
|
|
137
140
|
Ensure all required providers are registered in the container. If you use `createRequestScope()`, the child container can resolve tokens from the parent, but not vice versa.
|
package/dist/container.d.ts
CHANGED
|
@@ -16,10 +16,15 @@ export declare class Container {
|
|
|
16
16
|
private readonly staleDisposalErrors;
|
|
17
17
|
private readonly singletonCache;
|
|
18
18
|
private readonly forwardRefTokenCache;
|
|
19
|
+
private readonly providerLookupPlanCache;
|
|
20
|
+
private readonly multiProviderPlanCache;
|
|
21
|
+
private readonly requestScopeVerdictPlanCache;
|
|
22
|
+
private readonly effectiveProviderPlanCache;
|
|
19
23
|
private childScopes;
|
|
20
24
|
private disposePromise;
|
|
21
25
|
private disposed;
|
|
22
26
|
private trackedByRoot;
|
|
27
|
+
private graphRevision;
|
|
23
28
|
constructor(parent?: Container | undefined, requestScopeEnabled?: boolean, singletonCache?: Map<Token, Promise<unknown>>);
|
|
24
29
|
/**
|
|
25
30
|
* Registers providers in the current container scope.
|
|
@@ -137,12 +142,20 @@ export declare class Container {
|
|
|
137
142
|
private collectDisposableInstances;
|
|
138
143
|
private disposeInstancesInReverseOrder;
|
|
139
144
|
private clearDisposalCaches;
|
|
145
|
+
private currentLineageRevision;
|
|
146
|
+
private readCachedPlan;
|
|
147
|
+
private writePlanCache;
|
|
148
|
+
private advanceGraphRevision;
|
|
149
|
+
private clearResolutionPlanCaches;
|
|
140
150
|
private waitForStaleDisposalTasks;
|
|
141
151
|
private scheduleStaleDisposal;
|
|
142
152
|
private throwDisposalErrors;
|
|
153
|
+
private collectDisposalError;
|
|
143
154
|
private isDisposable;
|
|
144
155
|
private instantiate;
|
|
145
156
|
private assertSingletonDependencyScopes;
|
|
157
|
+
private findRequestScopedDependency;
|
|
158
|
+
private findRequestScopedDependencyToken;
|
|
146
159
|
private resolveEffectiveProvider;
|
|
147
160
|
private resolveProviderDependencyToken;
|
|
148
161
|
private resolveForwardRefToken;
|
package/dist/container.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAW3E,OAAO,KAAK,EASV,QAAQ,EAET,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"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"}
|
package/dist/container.js
CHANGED
|
@@ -126,10 +126,15 @@ export class Container {
|
|
|
126
126
|
staleDisposalErrors = [];
|
|
127
127
|
singletonCache;
|
|
128
128
|
forwardRefTokenCache = new WeakMap();
|
|
129
|
+
providerLookupPlanCache = new Map();
|
|
130
|
+
multiProviderPlanCache = new Map();
|
|
131
|
+
requestScopeVerdictPlanCache = new Map();
|
|
132
|
+
effectiveProviderPlanCache = new Map();
|
|
129
133
|
childScopes;
|
|
130
134
|
disposePromise;
|
|
131
135
|
disposed = false;
|
|
132
136
|
trackedByRoot = false;
|
|
137
|
+
graphRevision = 0;
|
|
133
138
|
constructor(parent, requestScopeEnabled = false, singletonCache) {
|
|
134
139
|
this.parent = parent;
|
|
135
140
|
this.requestScopeEnabled = requestScopeEnabled;
|
|
@@ -166,12 +171,14 @@ export class Container {
|
|
|
166
171
|
const existing = this.multiRegistrations.get(normalized.provide);
|
|
167
172
|
if (existing) {
|
|
168
173
|
existing.push(normalized);
|
|
174
|
+
this.advanceGraphRevision();
|
|
169
175
|
continue;
|
|
170
176
|
}
|
|
171
177
|
this.multiRegistrations.set(normalized.provide, [normalized]);
|
|
172
178
|
} else {
|
|
173
179
|
this.registrations.set(normalized.provide, normalized);
|
|
174
180
|
}
|
|
181
|
+
this.advanceGraphRevision();
|
|
175
182
|
}
|
|
176
183
|
return this;
|
|
177
184
|
}
|
|
@@ -205,10 +212,12 @@ export class Container {
|
|
|
205
212
|
if (normalized.multi) {
|
|
206
213
|
this.multiRegistrations.set(normalized.provide, [normalized]);
|
|
207
214
|
this.multiOverriddenTokens.add(normalized.provide);
|
|
215
|
+
this.advanceGraphRevision();
|
|
208
216
|
continue;
|
|
209
217
|
}
|
|
210
218
|
this.multiOverriddenTokens.add(normalized.provide);
|
|
211
219
|
this.registrations.set(normalized.provide, normalized);
|
|
220
|
+
this.advanceGraphRevision();
|
|
212
221
|
}
|
|
213
222
|
return this;
|
|
214
223
|
}
|
|
@@ -230,7 +239,11 @@ export class Container {
|
|
|
230
239
|
* @returns `true` when the provider graph contains request-scoped dependencies or is cyclic.
|
|
231
240
|
*/
|
|
232
241
|
hasRequestScopedDependency(token) {
|
|
233
|
-
|
|
242
|
+
const cached = this.readCachedPlan(this.requestScopeVerdictPlanCache, token);
|
|
243
|
+
if (cached) {
|
|
244
|
+
return cached.value;
|
|
245
|
+
}
|
|
246
|
+
return this.writePlanCache(this.requestScopeVerdictPlanCache, token, this.providerGraphRequiresRequestScope(token, new Set()));
|
|
234
247
|
}
|
|
235
248
|
|
|
236
249
|
/**
|
|
@@ -280,6 +293,7 @@ export class Container {
|
|
|
280
293
|
return;
|
|
281
294
|
}
|
|
282
295
|
this.disposed = true;
|
|
296
|
+
this.advanceGraphRevision();
|
|
283
297
|
this.disposePromise = this.disposeAll();
|
|
284
298
|
try {
|
|
285
299
|
await this.disposePromise;
|
|
@@ -289,13 +303,24 @@ export class Container {
|
|
|
289
303
|
}
|
|
290
304
|
}
|
|
291
305
|
async disposeAll() {
|
|
306
|
+
const errors = [];
|
|
292
307
|
try {
|
|
293
308
|
// Dispose all live request-scope children first (root only)
|
|
294
309
|
if (!this.parent && this.childScopes && this.childScopes.size > 0) {
|
|
295
|
-
await Promise.
|
|
310
|
+
const childResults = await Promise.allSettled(Array.from(this.childScopes).map(child => child.dispose()));
|
|
311
|
+
for (const result of childResults) {
|
|
312
|
+
if (result.status === 'rejected') {
|
|
313
|
+
this.collectDisposalError(result.reason, errors);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
296
316
|
this.childScopes.clear();
|
|
297
317
|
}
|
|
298
|
-
|
|
318
|
+
try {
|
|
319
|
+
await this.disposeCache(this.disposalCacheEntries());
|
|
320
|
+
} catch (error) {
|
|
321
|
+
this.collectDisposalError(error, errors);
|
|
322
|
+
}
|
|
323
|
+
this.throwDisposalErrors(errors);
|
|
299
324
|
} finally {
|
|
300
325
|
if (this.parent && this.trackedByRoot) {
|
|
301
326
|
this.root().childScopes?.delete(this);
|
|
@@ -345,15 +370,20 @@ export class Container {
|
|
|
345
370
|
return this.parent?.hasMultiRegistration(token) ?? false;
|
|
346
371
|
}
|
|
347
372
|
collectMultiProviders(token) {
|
|
373
|
+
const cached = this.readCachedPlan(this.multiProviderPlanCache, token);
|
|
374
|
+
if (cached) {
|
|
375
|
+
return [...cached.value];
|
|
376
|
+
}
|
|
348
377
|
const local = this.multiRegistrations.get(token);
|
|
378
|
+
let providers;
|
|
349
379
|
if (this.multiOverriddenTokens.has(token)) {
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
return [...fromParent, ...local];
|
|
380
|
+
providers = Object.freeze([...(local ?? [])]);
|
|
381
|
+
} else {
|
|
382
|
+
const fromParent = this.parent ? this.parent.collectMultiProviders(token) : [];
|
|
383
|
+
providers = Object.freeze(local ? [...fromParent, ...local] : [...fromParent]);
|
|
355
384
|
}
|
|
356
|
-
|
|
385
|
+
this.writePlanCache(this.multiProviderPlanCache, token, providers);
|
|
386
|
+
return [...providers];
|
|
357
387
|
}
|
|
358
388
|
providerGraphRequiresRequestScope(token, visited) {
|
|
359
389
|
if (visited.has(token)) {
|
|
@@ -559,11 +589,13 @@ export class Container {
|
|
|
559
589
|
return this.multiRequestCache;
|
|
560
590
|
}
|
|
561
591
|
lookupProvider(token) {
|
|
562
|
-
const
|
|
563
|
-
if (
|
|
564
|
-
return
|
|
592
|
+
const cached = this.readCachedPlan(this.providerLookupPlanCache, token);
|
|
593
|
+
if (cached) {
|
|
594
|
+
return cached.value;
|
|
565
595
|
}
|
|
566
|
-
|
|
596
|
+
const local = this.registrations.get(token);
|
|
597
|
+
const provider = local ?? this.parent?.lookupProvider(token);
|
|
598
|
+
return this.writePlanCache(this.providerLookupPlanCache, token, provider);
|
|
567
599
|
}
|
|
568
600
|
|
|
569
601
|
/**
|
|
@@ -673,10 +705,40 @@ export class Container {
|
|
|
673
705
|
if (this.parent) {
|
|
674
706
|
this.requestCache?.clear();
|
|
675
707
|
this.multiRequestCache?.clear();
|
|
708
|
+
this.clearResolutionPlanCaches();
|
|
676
709
|
return;
|
|
677
710
|
}
|
|
678
711
|
this.singletonCache.clear();
|
|
679
712
|
this.multiSingletonCache.clear();
|
|
713
|
+
this.clearResolutionPlanCaches();
|
|
714
|
+
}
|
|
715
|
+
currentLineageRevision() {
|
|
716
|
+
const parentRevision = this.parent?.currentLineageRevision();
|
|
717
|
+
return parentRevision ? `${parentRevision}/${this.graphRevision}` : String(this.graphRevision);
|
|
718
|
+
}
|
|
719
|
+
readCachedPlan(cache, token) {
|
|
720
|
+
const cached = cache.get(token);
|
|
721
|
+
if (!cached || cached.lineageRevision !== this.currentLineageRevision()) {
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
return cached;
|
|
725
|
+
}
|
|
726
|
+
writePlanCache(cache, token, value) {
|
|
727
|
+
cache.set(token, {
|
|
728
|
+
lineageRevision: this.currentLineageRevision(),
|
|
729
|
+
value
|
|
730
|
+
});
|
|
731
|
+
return value;
|
|
732
|
+
}
|
|
733
|
+
advanceGraphRevision() {
|
|
734
|
+
this.graphRevision += 1;
|
|
735
|
+
this.clearResolutionPlanCaches();
|
|
736
|
+
}
|
|
737
|
+
clearResolutionPlanCaches() {
|
|
738
|
+
this.providerLookupPlanCache.clear();
|
|
739
|
+
this.multiProviderPlanCache.clear();
|
|
740
|
+
this.requestScopeVerdictPlanCache.clear();
|
|
741
|
+
this.effectiveProviderPlanCache.clear();
|
|
680
742
|
}
|
|
681
743
|
async waitForStaleDisposalTasks() {
|
|
682
744
|
while (this.staleDisposalTasks.size > 0) {
|
|
@@ -707,6 +769,13 @@ export class Container {
|
|
|
707
769
|
throw new AggregateError(errors, 'Container disposal failed for one or more providers.');
|
|
708
770
|
}
|
|
709
771
|
}
|
|
772
|
+
collectDisposalError(error, errors) {
|
|
773
|
+
if (error instanceof AggregateError) {
|
|
774
|
+
errors.push(...error.errors);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
errors.push(error);
|
|
778
|
+
}
|
|
710
779
|
isDisposable(value) {
|
|
711
780
|
return typeof value === 'object' && value !== null && 'onDestroy' in value && typeof value.onDestroy === 'function';
|
|
712
781
|
}
|
|
@@ -741,19 +810,61 @@ export class Container {
|
|
|
741
810
|
if (provider.scope !== Scope.DEFAULT) {
|
|
742
811
|
return;
|
|
743
812
|
}
|
|
744
|
-
|
|
813
|
+
const requestScopedDependency = this.findRequestScopedDependency(provider.inject, new Set([provider.provide]));
|
|
814
|
+
if (requestScopedDependency) {
|
|
815
|
+
throw new ScopeMismatchError(`Singleton provider ${formatTokenName(provider.provide)} depends on request-scoped provider ${formatTokenName(requestScopedDependency)}.`, {
|
|
816
|
+
token: provider.provide,
|
|
817
|
+
scope: 'singleton',
|
|
818
|
+
hint: `Singleton providers cannot depend on request-scoped providers. Either change ${formatTokenName(requestScopedDependency)} to singleton/transient scope, or change ${formatTokenName(provider.provide)} to request scope.`
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
findRequestScopedDependency(depEntries, visited) {
|
|
823
|
+
for (const depEntry of depEntries) {
|
|
745
824
|
const depToken = this.resolveProviderDependencyToken(depEntry);
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
825
|
+
if (isOptionalToken(depEntry) && !this.has(depToken)) {
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
const requestScopedToken = this.findRequestScopedDependencyToken(depToken, visited);
|
|
829
|
+
if (requestScopedToken) {
|
|
830
|
+
return requestScopedToken;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return undefined;
|
|
834
|
+
}
|
|
835
|
+
findRequestScopedDependencyToken(token, visited) {
|
|
836
|
+
if (visited.has(token)) {
|
|
837
|
+
return undefined;
|
|
838
|
+
}
|
|
839
|
+
visited.add(token);
|
|
840
|
+
try {
|
|
841
|
+
const provider = this.resolveEffectiveProvider(token);
|
|
842
|
+
if (provider) {
|
|
843
|
+
if (provider.scope === Scope.REQUEST) {
|
|
844
|
+
return provider.provide;
|
|
845
|
+
}
|
|
846
|
+
return this.findRequestScopedDependency(provider.inject, visited);
|
|
847
|
+
}
|
|
848
|
+
if (typeof token !== 'function') {
|
|
849
|
+
return undefined;
|
|
850
|
+
}
|
|
851
|
+
const metadata = getClassDiMetadata(token);
|
|
852
|
+
if (metadata?.scope === Scope.REQUEST) {
|
|
853
|
+
return token;
|
|
753
854
|
}
|
|
855
|
+
return this.findRequestScopedDependency(metadata?.inject ?? [], visited);
|
|
856
|
+
} finally {
|
|
857
|
+
visited.delete(token);
|
|
754
858
|
}
|
|
755
859
|
}
|
|
756
860
|
resolveEffectiveProvider(token, visited = new Set(), chain = []) {
|
|
861
|
+
const cacheable = visited.size === 0 && chain.length === 0;
|
|
862
|
+
if (cacheable) {
|
|
863
|
+
const cached = this.readCachedPlan(this.effectiveProviderPlanCache, token);
|
|
864
|
+
if (cached) {
|
|
865
|
+
return cached.value;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
757
868
|
let currentToken = token;
|
|
758
869
|
while (true) {
|
|
759
870
|
if (visited.has(currentToken)) {
|
|
@@ -762,9 +873,15 @@ export class Container {
|
|
|
762
873
|
visited.add(currentToken);
|
|
763
874
|
const provider = this.lookupProvider(currentToken);
|
|
764
875
|
if (!provider) {
|
|
876
|
+
if (cacheable) {
|
|
877
|
+
return this.writePlanCache(this.effectiveProviderPlanCache, token, undefined);
|
|
878
|
+
}
|
|
765
879
|
return undefined;
|
|
766
880
|
}
|
|
767
881
|
if (provider.type !== 'existing' || provider.useExisting === undefined) {
|
|
882
|
+
if (cacheable) {
|
|
883
|
+
return this.writePlanCache(this.effectiveProviderPlanCache, token, provider);
|
|
884
|
+
}
|
|
768
885
|
return provider;
|
|
769
886
|
}
|
|
770
887
|
chain.push(currentToken);
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"container",
|
|
10
10
|
"provider"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.6",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fluojs/core": "^1.0.0-beta.
|
|
39
|
+
"@fluojs/core": "^1.0.0-beta.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"vitest": "^3.2.4"
|