@navios/di-react 0.1.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/LICENSE +7 -0
- package/README.md +176 -0
- package/lib/_tsup-dts-rollup.d.mts +304 -0
- package/lib/_tsup-dts-rollup.d.ts +304 -0
- package/lib/index.d.mts +18 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +405 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +392 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +44 -0
- package/project.json +61 -0
- package/src/hooks/__tests__/use-container.spec.mts +52 -0
- package/src/hooks/__tests__/use-invalidate.spec.mts +216 -0
- package/src/hooks/__tests__/use-optional-service.spec.mts +233 -0
- package/src/hooks/__tests__/use-service.spec.mts +212 -0
- package/src/hooks/__tests__/use-suspense-service.spec.mts +286 -0
- package/src/hooks/index.mts +8 -0
- package/src/hooks/use-container.mts +13 -0
- package/src/hooks/use-invalidate.mts +122 -0
- package/src/hooks/use-optional-service.mts +259 -0
- package/src/hooks/use-scope.mts +26 -0
- package/src/hooks/use-service.mts +201 -0
- package/src/hooks/use-suspense-service.mts +222 -0
- package/src/index.mts +8 -0
- package/src/providers/__tests__/scope-provider.spec.mts +280 -0
- package/src/providers/container-provider.mts +22 -0
- package/src/providers/context.mts +5 -0
- package/src/providers/index.mts +5 -0
- package/src/providers/scope-provider.mts +88 -0
- package/src/types.mts +21 -0
- package/tsconfig.json +13 -0
- package/tsup.config.mts +13 -0
- package/vitest.config.mts +9 -0
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { createContext, createElement, useContext, useId, useRef, useEffect, useReducer, useState, useCallback, useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/providers/context.mts
|
|
4
|
+
var ContainerContext = createContext(null);
|
|
5
|
+
function ContainerProvider({
|
|
6
|
+
container,
|
|
7
|
+
children
|
|
8
|
+
}) {
|
|
9
|
+
return createElement(
|
|
10
|
+
ContainerContext.Provider,
|
|
11
|
+
{ value: container },
|
|
12
|
+
children
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
function useContainer() {
|
|
16
|
+
const container = useContext(ContainerContext);
|
|
17
|
+
if (!container) {
|
|
18
|
+
throw new Error("useContainer must be used within a ContainerProvider");
|
|
19
|
+
}
|
|
20
|
+
return container;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/providers/scope-provider.mts
|
|
24
|
+
var ScopeContext = createContext(null);
|
|
25
|
+
function ScopeProvider({
|
|
26
|
+
scopeId,
|
|
27
|
+
metadata,
|
|
28
|
+
priority = 100,
|
|
29
|
+
children
|
|
30
|
+
}) {
|
|
31
|
+
const container = useContainer();
|
|
32
|
+
const generatedId = useId();
|
|
33
|
+
const effectiveScopeId = scopeId ?? generatedId;
|
|
34
|
+
const isInitializedRef = useRef(false);
|
|
35
|
+
if (!isInitializedRef.current) {
|
|
36
|
+
const existingContexts = container.getServiceLocator().getRequestContexts();
|
|
37
|
+
if (!existingContexts.has(effectiveScopeId)) {
|
|
38
|
+
container.beginRequest(effectiveScopeId, metadata, priority);
|
|
39
|
+
}
|
|
40
|
+
isInitializedRef.current = true;
|
|
41
|
+
}
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
return () => {
|
|
44
|
+
void container.endRequest(effectiveScopeId);
|
|
45
|
+
};
|
|
46
|
+
}, [container, effectiveScopeId]);
|
|
47
|
+
return createElement(
|
|
48
|
+
ScopeContext.Provider,
|
|
49
|
+
{ value: effectiveScopeId },
|
|
50
|
+
children
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function serviceReducer(state, action) {
|
|
54
|
+
switch (action.type) {
|
|
55
|
+
case "loading":
|
|
56
|
+
return { status: "loading" };
|
|
57
|
+
case "success":
|
|
58
|
+
return { status: "success", data: action.data };
|
|
59
|
+
case "error":
|
|
60
|
+
return { status: "error", error: action.error };
|
|
61
|
+
case "reset":
|
|
62
|
+
return { status: "idle" };
|
|
63
|
+
default:
|
|
64
|
+
return state;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function useService(token, args) {
|
|
68
|
+
const container = useContainer();
|
|
69
|
+
const serviceLocator = container.getServiceLocator();
|
|
70
|
+
const scopeId = useContext(ScopeContext);
|
|
71
|
+
const [state, dispatch] = useReducer(serviceReducer, { status: "idle" });
|
|
72
|
+
const instanceNameRef = useRef(null);
|
|
73
|
+
const [refetchCounter, setRefetchCounter] = useState(0);
|
|
74
|
+
{
|
|
75
|
+
const argsRef = useRef(args);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (argsRef.current !== args) {
|
|
78
|
+
if (JSON.stringify(argsRef.current) === JSON.stringify(args)) {
|
|
79
|
+
console.log(`WARNING: useService called with args that look the same but are different instances: ${JSON.stringify(argsRef.current)} !== ${JSON.stringify(args)}!
|
|
80
|
+
This is likely because you are using not memoized value that is not stable.
|
|
81
|
+
Please use a memoized value or use a different approach to pass the args.
|
|
82
|
+
Example:
|
|
83
|
+
const args = useMemo(() => ({ userId: '123' }), [])
|
|
84
|
+
return useService(UserToken, args)
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
argsRef.current = args;
|
|
88
|
+
}
|
|
89
|
+
}, [args]);
|
|
90
|
+
}
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const eventBus = serviceLocator.getEventBus();
|
|
93
|
+
let unsubscribe;
|
|
94
|
+
let isMounted = true;
|
|
95
|
+
const fetchAndSubscribe = async () => {
|
|
96
|
+
try {
|
|
97
|
+
if (scopeId) {
|
|
98
|
+
const requestContexts = serviceLocator.getRequestContexts();
|
|
99
|
+
if (requestContexts.has(scopeId)) {
|
|
100
|
+
container.setCurrentRequestContext(scopeId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const instance = await container.get(
|
|
104
|
+
// @ts-expect-error - token is valid
|
|
105
|
+
token,
|
|
106
|
+
args
|
|
107
|
+
);
|
|
108
|
+
if (!isMounted) return;
|
|
109
|
+
const instanceName = serviceLocator.getInstanceIdentifier(
|
|
110
|
+
token,
|
|
111
|
+
args
|
|
112
|
+
);
|
|
113
|
+
instanceNameRef.current = instanceName;
|
|
114
|
+
dispatch({ type: "success", data: instance });
|
|
115
|
+
unsubscribe = eventBus.on(instanceName, "destroy", () => {
|
|
116
|
+
if (isMounted) {
|
|
117
|
+
dispatch({ type: "loading" });
|
|
118
|
+
void fetchAndSubscribe();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (isMounted) {
|
|
123
|
+
dispatch({ type: "error", error });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
dispatch({ type: "loading" });
|
|
128
|
+
void fetchAndSubscribe();
|
|
129
|
+
return () => {
|
|
130
|
+
isMounted = false;
|
|
131
|
+
unsubscribe?.();
|
|
132
|
+
};
|
|
133
|
+
}, [container, serviceLocator, token, args, scopeId, refetchCounter]);
|
|
134
|
+
const refetch = useCallback(() => {
|
|
135
|
+
setRefetchCounter((c) => c + 1);
|
|
136
|
+
}, []);
|
|
137
|
+
return {
|
|
138
|
+
data: state.status === "success" ? state.data : void 0,
|
|
139
|
+
error: state.status === "error" ? state.error : void 0,
|
|
140
|
+
isLoading: state.status === "loading",
|
|
141
|
+
isSuccess: state.status === "success",
|
|
142
|
+
isError: state.status === "error",
|
|
143
|
+
refetch
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
var cacheMap = /* @__PURE__ */ new WeakMap();
|
|
147
|
+
function getCacheKey(token, args) {
|
|
148
|
+
const tokenId = typeof token === "function" ? token.name : token.id || token.token?.id || String(token);
|
|
149
|
+
return `${tokenId}:${JSON.stringify(args ?? null)}`;
|
|
150
|
+
}
|
|
151
|
+
function getCache(container) {
|
|
152
|
+
let cache = cacheMap.get(container);
|
|
153
|
+
if (!cache) {
|
|
154
|
+
cache = /* @__PURE__ */ new Map();
|
|
155
|
+
cacheMap.set(container, cache);
|
|
156
|
+
}
|
|
157
|
+
return cache;
|
|
158
|
+
}
|
|
159
|
+
function useSuspenseService(token, args) {
|
|
160
|
+
const container = useContainer();
|
|
161
|
+
const serviceLocator = container.getServiceLocator();
|
|
162
|
+
const cache = getCache(container);
|
|
163
|
+
const cacheKey = getCacheKey(token, args);
|
|
164
|
+
const entryRef = useRef(null);
|
|
165
|
+
{
|
|
166
|
+
const argsRef = useRef(args);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (argsRef.current !== args) {
|
|
169
|
+
if (JSON.stringify(argsRef.current) === JSON.stringify(args)) {
|
|
170
|
+
console.log(`WARNING: useService called with args that look the same but are different instances: ${JSON.stringify(argsRef.current)} !== ${JSON.stringify(args)}!
|
|
171
|
+
This is likely because you are using not memoized value that is not stable.
|
|
172
|
+
Please use a memoized value or use a different approach to pass the args.
|
|
173
|
+
Example:
|
|
174
|
+
const args = useMemo(() => ({ userId: '123' }), [])
|
|
175
|
+
return useService(UserToken, args)
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
178
|
+
argsRef.current = args;
|
|
179
|
+
}
|
|
180
|
+
}, [args]);
|
|
181
|
+
}
|
|
182
|
+
if (!cache.has(cacheKey)) {
|
|
183
|
+
const entry2 = {
|
|
184
|
+
promise: null,
|
|
185
|
+
result: void 0,
|
|
186
|
+
error: void 0,
|
|
187
|
+
status: "pending",
|
|
188
|
+
version: 0,
|
|
189
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
190
|
+
instanceName: null,
|
|
191
|
+
unsubscribe: void 0
|
|
192
|
+
};
|
|
193
|
+
cache.set(cacheKey, entry2);
|
|
194
|
+
}
|
|
195
|
+
const entry = cache.get(cacheKey);
|
|
196
|
+
entryRef.current = entry;
|
|
197
|
+
const fetchService = useCallback(() => {
|
|
198
|
+
const currentEntry = entryRef.current;
|
|
199
|
+
if (!currentEntry) return;
|
|
200
|
+
currentEntry.status = "pending";
|
|
201
|
+
currentEntry.version++;
|
|
202
|
+
currentEntry.promise = container.get(token, args).then((instance) => {
|
|
203
|
+
currentEntry.result = instance;
|
|
204
|
+
currentEntry.status = "resolved";
|
|
205
|
+
currentEntry.instanceName = serviceLocator.getInstanceIdentifier(
|
|
206
|
+
token,
|
|
207
|
+
args
|
|
208
|
+
);
|
|
209
|
+
if (!currentEntry.unsubscribe && currentEntry.instanceName) {
|
|
210
|
+
const eventBus = serviceLocator.getEventBus();
|
|
211
|
+
currentEntry.unsubscribe = eventBus.on(
|
|
212
|
+
currentEntry.instanceName,
|
|
213
|
+
"destroy",
|
|
214
|
+
() => {
|
|
215
|
+
currentEntry.result = void 0;
|
|
216
|
+
currentEntry.error = void 0;
|
|
217
|
+
currentEntry.status = "pending";
|
|
218
|
+
currentEntry.promise = null;
|
|
219
|
+
currentEntry.subscribers.forEach((callback) => callback());
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
currentEntry.subscribers.forEach((callback) => callback());
|
|
224
|
+
return instance;
|
|
225
|
+
}).catch((error) => {
|
|
226
|
+
currentEntry.error = error;
|
|
227
|
+
currentEntry.status = "rejected";
|
|
228
|
+
throw error;
|
|
229
|
+
});
|
|
230
|
+
return currentEntry.promise;
|
|
231
|
+
}, [container, serviceLocator, token, args]);
|
|
232
|
+
const subscribe = useCallback(
|
|
233
|
+
(callback) => {
|
|
234
|
+
entry.subscribers.add(callback);
|
|
235
|
+
return () => {
|
|
236
|
+
entry.subscribers.delete(callback);
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
[entry]
|
|
240
|
+
);
|
|
241
|
+
const getSnapshot = useCallback(() => {
|
|
242
|
+
return `${entry.status}:${entry.version}`;
|
|
243
|
+
}, [entry]);
|
|
244
|
+
useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
return () => {
|
|
247
|
+
if (entry.subscribers.size === 0) {
|
|
248
|
+
entry.unsubscribe?.();
|
|
249
|
+
cache.delete(cacheKey);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}, [entry]);
|
|
253
|
+
if (entry.status === "pending" && !entry.promise) {
|
|
254
|
+
fetchService();
|
|
255
|
+
}
|
|
256
|
+
if (entry.status === "pending") {
|
|
257
|
+
throw entry.promise;
|
|
258
|
+
}
|
|
259
|
+
if (entry.status === "rejected") {
|
|
260
|
+
throw entry.error;
|
|
261
|
+
}
|
|
262
|
+
return entry.result;
|
|
263
|
+
}
|
|
264
|
+
function optionalServiceReducer(state, action) {
|
|
265
|
+
switch (action.type) {
|
|
266
|
+
case "loading":
|
|
267
|
+
return { status: "loading" };
|
|
268
|
+
case "success":
|
|
269
|
+
return { status: "success", data: action.data };
|
|
270
|
+
case "not-found":
|
|
271
|
+
return { status: "not-found" };
|
|
272
|
+
case "error":
|
|
273
|
+
return { status: "error", error: action.error };
|
|
274
|
+
case "reset":
|
|
275
|
+
return { status: "idle" };
|
|
276
|
+
default:
|
|
277
|
+
return state;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function useOptionalService(token, args) {
|
|
281
|
+
const container = useContainer();
|
|
282
|
+
const serviceLocator = container.getServiceLocator();
|
|
283
|
+
const [state, dispatch] = useReducer(optionalServiceReducer, { status: "idle" });
|
|
284
|
+
const instanceNameRef = useRef(null);
|
|
285
|
+
{
|
|
286
|
+
const argsRef = useRef(args);
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (argsRef.current !== args) {
|
|
289
|
+
if (JSON.stringify(argsRef.current) === JSON.stringify(args)) {
|
|
290
|
+
console.log(`WARNING: useOptionalService called with args that look the same but are different instances: ${JSON.stringify(argsRef.current)} !== ${JSON.stringify(args)}!
|
|
291
|
+
This is likely because you are using not memoized value that is not stable.
|
|
292
|
+
Please use a memoized value or use a different approach to pass the args.
|
|
293
|
+
Example:
|
|
294
|
+
const args = useMemo(() => ({ userId: '123' }), [])
|
|
295
|
+
return useOptionalService(UserToken, args)
|
|
296
|
+
`);
|
|
297
|
+
}
|
|
298
|
+
argsRef.current = args;
|
|
299
|
+
}
|
|
300
|
+
}, [args]);
|
|
301
|
+
}
|
|
302
|
+
const fetchService = useCallback(async () => {
|
|
303
|
+
dispatch({ type: "loading" });
|
|
304
|
+
try {
|
|
305
|
+
const [error, instance] = await serviceLocator.getInstance(
|
|
306
|
+
token,
|
|
307
|
+
args
|
|
308
|
+
);
|
|
309
|
+
if (error) {
|
|
310
|
+
const errorMessage = error.message?.toLowerCase() ?? "";
|
|
311
|
+
if (errorMessage.includes("not found") || errorMessage.includes("not registered") || errorMessage.includes("no provider")) {
|
|
312
|
+
dispatch({ type: "not-found" });
|
|
313
|
+
} else {
|
|
314
|
+
dispatch({ type: "error", error });
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
instanceNameRef.current = serviceLocator.getInstanceIdentifier(
|
|
319
|
+
token,
|
|
320
|
+
args
|
|
321
|
+
);
|
|
322
|
+
dispatch({ type: "success", data: instance });
|
|
323
|
+
} catch (error) {
|
|
324
|
+
const err = error;
|
|
325
|
+
const errorMessage = err.message?.toLowerCase() ?? "";
|
|
326
|
+
if (errorMessage.includes("not found") || errorMessage.includes("not registered") || errorMessage.includes("no provider")) {
|
|
327
|
+
dispatch({ type: "not-found" });
|
|
328
|
+
} else {
|
|
329
|
+
dispatch({ type: "error", error: err });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}, [serviceLocator, token, args]);
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
const eventBus = serviceLocator.getEventBus();
|
|
335
|
+
let unsubscribe;
|
|
336
|
+
void fetchService();
|
|
337
|
+
const setupSubscription = () => {
|
|
338
|
+
if (instanceNameRef.current) {
|
|
339
|
+
unsubscribe = eventBus.on(instanceNameRef.current, "destroy", () => {
|
|
340
|
+
void fetchService();
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
const timeoutId = setTimeout(setupSubscription, 10);
|
|
345
|
+
return () => {
|
|
346
|
+
clearTimeout(timeoutId);
|
|
347
|
+
unsubscribe?.();
|
|
348
|
+
};
|
|
349
|
+
}, [fetchService, serviceLocator]);
|
|
350
|
+
return {
|
|
351
|
+
data: state.status === "success" ? state.data : void 0,
|
|
352
|
+
error: state.status === "error" ? state.error : void 0,
|
|
353
|
+
isLoading: state.status === "loading",
|
|
354
|
+
isSuccess: state.status === "success",
|
|
355
|
+
isNotFound: state.status === "not-found",
|
|
356
|
+
isError: state.status === "error",
|
|
357
|
+
refetch: fetchService
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function useInvalidate(token, args) {
|
|
361
|
+
const container = useContainer();
|
|
362
|
+
const serviceLocator = container.getServiceLocator();
|
|
363
|
+
return useCallback(async () => {
|
|
364
|
+
const instanceName = serviceLocator.getInstanceIdentifier(token, args);
|
|
365
|
+
await serviceLocator.invalidate(instanceName);
|
|
366
|
+
}, [serviceLocator, token, args]);
|
|
367
|
+
}
|
|
368
|
+
function useInvalidateInstance() {
|
|
369
|
+
const container = useContainer();
|
|
370
|
+
return useCallback(
|
|
371
|
+
async (instance) => {
|
|
372
|
+
await container.invalidate(instance);
|
|
373
|
+
},
|
|
374
|
+
[container]
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
function useScope() {
|
|
378
|
+
return useContext(ScopeContext);
|
|
379
|
+
}
|
|
380
|
+
function useScopeOrThrow() {
|
|
381
|
+
const scope = useScope();
|
|
382
|
+
if (scope === null) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
"useScopeOrThrow must be used within a ScopeProvider. Wrap your component tree with <ScopeProvider> to create a request scope."
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return scope;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export { ContainerContext, ContainerProvider, ScopeContext, ScopeProvider, useContainer, useInvalidate, useInvalidateInstance, useOptionalService, useScope, useScopeOrThrow, useService, useSuspenseService };
|
|
391
|
+
//# sourceMappingURL=index.mjs.map
|
|
392
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/context.mts","../src/providers/container-provider.mts","../src/hooks/use-container.mts","../src/providers/scope-provider.mts","../src/hooks/use-service.mts","../src/hooks/use-suspense-service.mts","../src/hooks/use-optional-service.mts","../src/hooks/use-invalidate.mts","../src/hooks/use-scope.mts"],"names":["createContext","createElement","useContext","useRef","useEffect","entry","useCallback","useReducer"],"mappings":"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","file":"index.mjs","sourcesContent":["import { createContext } from 'react'\n\nimport type { Container } from '@navios/di'\n\nexport const ContainerContext = createContext<Container | null>(null)\n","import type { Container } from '@navios/di'\nimport type { ReactNode } from 'react'\n\nimport { createElement } from 'react'\n\nimport { ContainerContext } from './context.mjs'\n\nexport interface ContainerProviderProps {\n container: Container\n children: ReactNode\n}\n\nexport function ContainerProvider({\n container,\n children,\n}: ContainerProviderProps) {\n return createElement(\n ContainerContext.Provider,\n { value: container },\n children,\n )\n}\n","import { useContext } from 'react'\n\nimport { ContainerContext } from '../providers/context.mjs'\n\nexport function useContainer() {\n const container = useContext(ContainerContext)\n\n if (!container) {\n throw new Error('useContainer must be used within a ContainerProvider')\n }\n\n return container\n}\n","import type { ReactNode } from 'react'\n\nimport { createContext, createElement, useEffect, useId, useRef } from 'react'\n\nimport { useContainer } from '../hooks/use-container.mjs'\n\n/**\n * Context for the current scope ID.\n * This allows nested components to access the current request scope.\n */\nexport const ScopeContext = createContext<string | null>(null)\n\nexport interface ScopeProviderProps {\n /**\n * Optional explicit scope ID. If not provided, a unique ID will be generated.\n * Useful when you need to reference the scope externally.\n */\n scopeId?: string\n /**\n * Optional metadata to attach to the request context.\n * Can be used to pass data like user info, request headers, etc.\n */\n metadata?: Record<string, unknown>\n /**\n * Priority for service resolution. Higher priority scopes take precedence.\n * @default 100\n */\n priority?: number\n children: ReactNode\n}\n\n/**\n * ScopeProvider creates a new request scope for dependency injection.\n *\n * Services with `scope: 'Request'` will be instantiated once per ScopeProvider\n * and shared among all components within that provider.\n *\n * This is useful for:\n * - Table rows that need isolated state\n * - Modal dialogs with their own service instances\n * - Multi-tenant scenarios\n * - Any case where you need isolated service instances\n *\n * @example\n * ```tsx\n * // Each row gets its own RowStateService instance\n * {rows.map(row => (\n * <ScopeProvider key={row.id} scopeId={row.id}>\n * <TableRow data={row} />\n * </ScopeProvider>\n * ))}\n * ```\n */\nexport function ScopeProvider({\n scopeId,\n metadata,\n priority = 100,\n children,\n}: ScopeProviderProps) {\n const container = useContainer()\n const generatedId = useId()\n const effectiveScopeId = scopeId ?? generatedId\n const isInitializedRef = useRef(false)\n\n // Begin request context on first render only\n // We use a ref to track initialization to handle React StrictMode double-renders\n if (!isInitializedRef.current) {\n // Check if context already exists (e.g., from StrictMode double render)\n const existingContexts = container.getServiceLocator().getRequestContexts()\n if (!existingContexts.has(effectiveScopeId)) {\n container.beginRequest(effectiveScopeId, metadata, priority)\n }\n isInitializedRef.current = true\n }\n\n // End request context on unmount\n useEffect(() => {\n return () => {\n void container.endRequest(effectiveScopeId)\n }\n }, [container, effectiveScopeId])\n\n return createElement(\n ScopeContext.Provider,\n { value: effectiveScopeId },\n children,\n )\n}\n","import type {\n AnyInjectableType,\n BoundInjectionToken,\n ClassType,\n Factorable,\n FactoryInjectionToken,\n InjectionToken,\n InjectionTokenSchemaType,\n} from '@navios/di'\nimport type { z, ZodType } from 'zod/v4'\n\nimport { useCallback, useContext, useEffect, useReducer, useRef, useState } from 'react'\n\nimport type { Join, UnionToArray } from '../types.mjs'\n\nimport { ScopeContext } from '../providers/scope-provider.mjs'\nimport { useContainer } from './use-container.mjs'\n\ntype ServiceState<T> =\n | { status: 'idle' }\n | { status: 'loading' }\n | { status: 'success'; data: T }\n | { status: 'error'; error: Error }\n\ntype ServiceAction<T> =\n | { type: 'loading' }\n | { type: 'success'; data: T }\n | { type: 'error'; error: Error }\n | { type: 'reset' }\n\nfunction serviceReducer<T>(\n state: ServiceState<T>,\n action: ServiceAction<T>,\n): ServiceState<T> {\n switch (action.type) {\n case 'loading':\n return { status: 'loading' }\n case 'success':\n return { status: 'success', data: action.data }\n case 'error':\n return { status: 'error', error: action.error }\n case 'reset':\n return { status: 'idle' }\n default:\n return state\n }\n}\n\nexport interface UseServiceResult<T> {\n data: T | undefined\n error: Error | undefined\n isLoading: boolean\n isSuccess: boolean\n isError: boolean\n refetch: () => void\n}\n\n// #1 Simple class\nexport function useService<T extends ClassType>(\n token: T,\n): UseServiceResult<\n InstanceType<T> extends Factorable<infer R> ? R : InstanceType<T>\n>\n\n// #2 Token with required Schema\nexport function useService<T, S extends InjectionTokenSchemaType>(\n token: InjectionToken<T, S>,\n args: z.input<S>,\n): UseServiceResult<T>\n\n// #3 Token with optional Schema\nexport function useService<\n T,\n S extends InjectionTokenSchemaType,\n R extends boolean,\n>(\n token: InjectionToken<T, S, R>,\n): R extends false\n ? UseServiceResult<T>\n : S extends ZodType<infer Type>\n ? `Error: Your token requires args: ${Join<UnionToArray<keyof Type>, ', '>}`\n : 'Error: Your token requires args'\n\n// #4 Token with no Schema\nexport function useService<T>(\n token: InjectionToken<T, undefined>,\n): UseServiceResult<T>\n\nexport function useService<T>(\n token: BoundInjectionToken<T, any>,\n): UseServiceResult<T>\n\nexport function useService<T>(\n token: FactoryInjectionToken<T, any>,\n): UseServiceResult<T>\n\nexport function useService(\n token:\n | ClassType\n | InjectionToken<any, any>\n | BoundInjectionToken<any, any>\n | FactoryInjectionToken<any, any>,\n args?: unknown,\n): UseServiceResult<any> {\n const container = useContainer()\n const serviceLocator = container.getServiceLocator()\n const scopeId = useContext(ScopeContext)\n const [state, dispatch] = useReducer(serviceReducer, { status: 'idle' })\n const instanceNameRef = useRef<string | null>(null)\n const [refetchCounter, setRefetchCounter] = useState(0)\n\n if (process.env.NODE_ENV === 'development') {\n const argsRef = useRef<unknown>(args)\n useEffect(() => {\n if (argsRef.current !== args) {\n if (JSON.stringify(argsRef.current) === JSON.stringify(args)) {\n console.log(`WARNING: useService called with args that look the same but are different instances: ${JSON.stringify(argsRef.current)} !== ${JSON.stringify(args)}!\n This is likely because you are using not memoized value that is not stable.\n Please use a memoized value or use a different approach to pass the args.\n Example:\n const args = useMemo(() => ({ userId: '123' }), [])\n return useService(UserToken, args)\n `)\n }\n argsRef.current = args\n }\n }, [args])\n }\n\n // Subscribe to invalidation events\n useEffect(() => {\n const eventBus = serviceLocator.getEventBus()\n let unsubscribe: (() => void) | undefined\n let isMounted = true\n\n // Fetch the service and set up subscription\n const fetchAndSubscribe = async () => {\n try {\n // Set the correct request context before getting the instance\n // This ensures request-scoped services are resolved in the correct scope\n if (scopeId) {\n const requestContexts = serviceLocator.getRequestContexts()\n if (requestContexts.has(scopeId)) {\n container.setCurrentRequestContext(scopeId)\n }\n }\n\n const instance = await container.get(\n // @ts-expect-error - token is valid\n token as AnyInjectableType,\n args as any,\n )\n\n if (!isMounted) return\n\n // Get instance name for event subscription\n const instanceName = serviceLocator.getInstanceIdentifier(\n token as AnyInjectableType,\n args,\n )\n instanceNameRef.current = instanceName\n\n dispatch({ type: 'success', data: instance })\n\n // Set up subscription after we have the instance\n unsubscribe = eventBus.on(instanceName, 'destroy', () => {\n // Re-fetch when the service is invalidated\n if (isMounted) {\n dispatch({ type: 'loading' })\n void fetchAndSubscribe()\n }\n })\n } catch (error) {\n if (isMounted) {\n dispatch({ type: 'error', error: error as Error })\n }\n }\n }\n\n dispatch({ type: 'loading' })\n void fetchAndSubscribe()\n\n return () => {\n isMounted = false\n unsubscribe?.()\n }\n }, [container, serviceLocator, token, args, scopeId, refetchCounter])\n\n const refetch = useCallback(() => {\n setRefetchCounter((c) => c + 1)\n }, [])\n\n return {\n data: state.status === 'success' ? state.data : undefined,\n error: state.status === 'error' ? state.error : undefined,\n isLoading: state.status === 'loading',\n isSuccess: state.status === 'success',\n isError: state.status === 'error',\n refetch,\n }\n}\n","import type {\n AnyInjectableType,\n BoundInjectionToken,\n ClassType,\n Factorable,\n FactoryInjectionToken,\n InjectionToken,\n InjectionTokenSchemaType,\n} from '@navios/di'\nimport type { z, ZodType } from 'zod/v4'\n\nimport { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\n\nimport type { Join, UnionToArray } from '../types.mjs'\n\nimport { useContainer } from './use-container.mjs'\n\n// Cache entry for suspense\ninterface CacheEntry<T> {\n promise: Promise<T> | null\n result: T | undefined\n error: Error | undefined\n status: 'pending' | 'resolved' | 'rejected'\n version: number // Increment on each fetch to track changes\n subscribers: Set<() => void>\n instanceName: string | null\n unsubscribe: (() => void) | undefined\n}\n\n// Global cache for service instances (per container + token + args combination)\nconst cacheMap = new WeakMap<object, Map<string, CacheEntry<any>>>()\n\nfunction getCacheKey(token: any, args: unknown): string {\n const tokenId =\n typeof token === 'function'\n ? token.name\n : token.id || token.token?.id || String(token)\n return `${tokenId}:${JSON.stringify(args ?? null)}`\n}\n\nfunction getCache(container: object): Map<string, CacheEntry<any>> {\n let cache = cacheMap.get(container)\n if (!cache) {\n cache = new Map()\n cacheMap.set(container, cache)\n }\n return cache\n}\n\n// #1 Simple class\nexport function useSuspenseService<T extends ClassType>(\n token: T,\n): InstanceType<T> extends Factorable<infer R> ? R : InstanceType<T>\n\n// #2 Token with required Schema\nexport function useSuspenseService<T, S extends InjectionTokenSchemaType>(\n token: InjectionToken<T, S>,\n args: z.input<S>,\n): T\n\n// #3 Token with optional Schema\nexport function useSuspenseService<\n T,\n S extends InjectionTokenSchemaType,\n R extends boolean,\n>(\n token: InjectionToken<T, S, R>,\n): R extends false\n ? T\n : S extends ZodType<infer Type>\n ? `Error: Your token requires args: ${Join<UnionToArray<keyof Type>, ', '>}`\n : 'Error: Your token requires args'\n\n// #4 Token with no Schema\nexport function useSuspenseService<T>(token: InjectionToken<T, undefined>): T\n\nexport function useSuspenseService<T>(token: BoundInjectionToken<T, any>): T\n\nexport function useSuspenseService<T>(token: FactoryInjectionToken<T, any>): T\n\nexport function useSuspenseService(\n token:\n | ClassType\n | InjectionToken<any, any>\n | BoundInjectionToken<any, any>\n | FactoryInjectionToken<any, any>,\n args?: unknown,\n): any {\n const container = useContainer()\n const serviceLocator = container.getServiceLocator()\n const cache = getCache(container)\n const cacheKey = getCacheKey(token, args)\n const entryRef = useRef<CacheEntry<any> | null>(null)\n if (process.env.NODE_ENV === 'development') {\n const argsRef = useRef<unknown>(args)\n useEffect(() => {\n if (argsRef.current !== args) {\n if (JSON.stringify(argsRef.current) === JSON.stringify(args)) {\n console.log(`WARNING: useService called with args that look the same but are different instances: ${JSON.stringify(argsRef.current)} !== ${JSON.stringify(args)}!\n This is likely because you are using not memoized value that is not stable.\n Please use a memoized value or use a different approach to pass the args.\n Example:\n const args = useMemo(() => ({ userId: '123' }), [])\n return useService(UserToken, args)\n `)\n }\n argsRef.current = args\n }\n }, [args])\n }\n\n // Initialize or get cache entry\n if (!cache.has(cacheKey)) {\n const entry: CacheEntry<any> = {\n promise: null,\n result: undefined,\n error: undefined,\n status: 'pending',\n version: 0,\n subscribers: new Set(),\n instanceName: null,\n unsubscribe: undefined,\n }\n cache.set(cacheKey, entry)\n }\n\n const entry = cache.get(cacheKey)!\n entryRef.current = entry\n\n // Function to fetch the service\n const fetchService = useCallback(() => {\n const currentEntry = entryRef.current\n if (!currentEntry) return\n\n currentEntry.status = 'pending'\n currentEntry.version++ // Increment version to signal change to useSyncExternalStore\n currentEntry.promise = (container.get as any)(token, args)\n .then((instance: any) => {\n currentEntry.result = instance\n currentEntry.status = 'resolved'\n currentEntry.instanceName = serviceLocator.getInstanceIdentifier(\n token as AnyInjectableType,\n args,\n )\n\n // Subscribe to invalidation events if not already subscribed\n if (!currentEntry.unsubscribe && currentEntry.instanceName) {\n const eventBus = serviceLocator.getEventBus()\n currentEntry.unsubscribe = eventBus.on(\n currentEntry.instanceName,\n 'destroy',\n () => {\n // Clear cache and notify subscribers to re-fetch\n currentEntry.result = undefined\n currentEntry.error = undefined\n currentEntry.status = 'pending'\n currentEntry.promise = null\n // Notify all subscribers\n currentEntry.subscribers.forEach((callback) => callback())\n },\n )\n }\n\n // Notify subscribers\n currentEntry.subscribers.forEach((callback) => callback())\n return instance\n })\n .catch((error: Error) => {\n currentEntry.error = error\n currentEntry.status = 'rejected'\n throw error\n })\n\n return currentEntry.promise\n }, [container, serviceLocator, token, args])\n\n // Subscribe to cache changes\n const subscribe = useCallback(\n (callback: () => void) => {\n entry.subscribers.add(callback)\n return () => {\n entry.subscribers.delete(callback)\n }\n },\n [entry],\n )\n\n // Get snapshot of current state - include version to detect invalidation\n const getSnapshot = useCallback(() => {\n return `${entry.status}:${entry.version}`\n }, [entry])\n\n // Use sync external store to track cache state\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n // Cleanup subscription on unmount\n useEffect(() => {\n return () => {\n // If there are no subscribers, unsubscribe and delete the cache entry\n if (entry.subscribers.size === 0) {\n entry.unsubscribe?.()\n cache.delete(cacheKey)\n }\n }\n }, [entry])\n\n // Start fetching if not already\n if (entry.status === 'pending' && !entry.promise) {\n fetchService()\n }\n\n // Suspense behavior\n if (entry.status === 'pending') {\n throw entry.promise\n }\n\n if (entry.status === 'rejected') {\n throw entry.error\n }\n\n return entry.result\n}\n","import type {\n AnyInjectableType,\n BoundInjectionToken,\n ClassType,\n Factorable,\n FactoryInjectionToken,\n InjectionToken,\n InjectionTokenSchemaType,\n} from '@navios/di'\nimport type { z, ZodType } from 'zod/v4'\n\nimport { useCallback, useEffect, useReducer, useRef } from 'react'\n\nimport type { Join, UnionToArray } from '../types.mjs'\n\nimport { useContainer } from './use-container.mjs'\n\ntype OptionalServiceState<T> =\n | { status: 'idle' }\n | { status: 'loading' }\n | { status: 'success'; data: T }\n | { status: 'not-found' }\n | { status: 'error'; error: Error }\n\ntype OptionalServiceAction<T> =\n | { type: 'loading' }\n | { type: 'success'; data: T }\n | { type: 'not-found' }\n | { type: 'error'; error: Error }\n | { type: 'reset' }\n\nfunction optionalServiceReducer<T>(\n state: OptionalServiceState<T>,\n action: OptionalServiceAction<T>,\n): OptionalServiceState<T> {\n switch (action.type) {\n case 'loading':\n return { status: 'loading' }\n case 'success':\n return { status: 'success', data: action.data }\n case 'not-found':\n return { status: 'not-found' }\n case 'error':\n return { status: 'error', error: action.error }\n case 'reset':\n return { status: 'idle' }\n default:\n return state\n }\n}\n\nexport interface UseOptionalServiceResult<T> {\n /**\n * The service instance if found and loaded successfully, otherwise undefined.\n */\n data: T | undefined\n /**\n * Error that occurred during loading (excludes \"not found\" which is not an error).\n */\n error: Error | undefined\n /**\n * True while the service is being loaded.\n */\n isLoading: boolean\n /**\n * True if the service was loaded successfully.\n */\n isSuccess: boolean\n /**\n * True if the service was not found (not registered in the container).\n */\n isNotFound: boolean\n /**\n * True if an error occurred during loading.\n */\n isError: boolean\n /**\n * Function to manually re-fetch the service.\n */\n refetch: () => void\n}\n\n// #1 Simple class\nexport function useOptionalService<T extends ClassType>(\n token: T,\n): UseOptionalServiceResult<\n InstanceType<T> extends Factorable<infer R> ? R : InstanceType<T>\n>\n\n// #2 Token with required Schema\nexport function useOptionalService<T, S extends InjectionTokenSchemaType>(\n token: InjectionToken<T, S>,\n args: z.input<S>,\n): UseOptionalServiceResult<T>\n\n// #3 Token with optional Schema\nexport function useOptionalService<\n T,\n S extends InjectionTokenSchemaType,\n R extends boolean,\n>(\n token: InjectionToken<T, S, R>,\n): R extends false\n ? UseOptionalServiceResult<T>\n : S extends ZodType<infer Type>\n ? `Error: Your token requires args: ${Join<UnionToArray<keyof Type>, ', '>}`\n : 'Error: Your token requires args'\n\n// #4 Token with no Schema\nexport function useOptionalService<T>(\n token: InjectionToken<T, undefined>,\n): UseOptionalServiceResult<T>\n\nexport function useOptionalService<T>(\n token: BoundInjectionToken<T, any>,\n): UseOptionalServiceResult<T>\n\nexport function useOptionalService<T>(\n token: FactoryInjectionToken<T, any>,\n): UseOptionalServiceResult<T>\n\n/**\n * Hook to optionally load a service from the DI container.\n *\n * Unlike useService, this hook does NOT throw an error if the service is not registered.\n * Instead, it returns `isNotFound: true` when the service doesn't exist.\n *\n * This is useful for:\n * - Optional dependencies that may or may not be configured\n * - Feature flags where a service might not be available\n * - Plugins or extensions that are conditionally registered\n *\n * @example\n * ```tsx\n * function Analytics() {\n * const { data: analytics, isNotFound } = useOptionalService(AnalyticsService)\n *\n * if (isNotFound) {\n * // Analytics service not configured, skip tracking\n * return null\n * }\n *\n * return <AnalyticsTracker service={analytics} />\n * }\n * ```\n */\nexport function useOptionalService(\n token:\n | ClassType\n | InjectionToken<any, any>\n | BoundInjectionToken<any, any>\n | FactoryInjectionToken<any, any>,\n args?: unknown,\n): UseOptionalServiceResult<any> {\n const container = useContainer()\n const serviceLocator = container.getServiceLocator()\n const [state, dispatch] = useReducer(optionalServiceReducer, { status: 'idle' })\n const instanceNameRef = useRef<string | null>(null)\n\n if (process.env.NODE_ENV === 'development') {\n const argsRef = useRef<unknown>(args)\n useEffect(() => {\n if (argsRef.current !== args) {\n if (JSON.stringify(argsRef.current) === JSON.stringify(args)) {\n console.log(`WARNING: useOptionalService called with args that look the same but are different instances: ${JSON.stringify(argsRef.current)} !== ${JSON.stringify(args)}!\n This is likely because you are using not memoized value that is not stable.\n Please use a memoized value or use a different approach to pass the args.\n Example:\n const args = useMemo(() => ({ userId: '123' }), [])\n return useOptionalService(UserToken, args)\n `)\n }\n argsRef.current = args\n }\n }, [args])\n }\n\n const fetchService = useCallback(async () => {\n dispatch({ type: 'loading' })\n try {\n const [error, instance] = await serviceLocator.getInstance(\n token as AnyInjectableType,\n args as any,\n )\n\n if (error) {\n // Check if error is a \"not found\" type error\n const errorMessage = error.message?.toLowerCase() ?? ''\n if (\n errorMessage.includes('not found') ||\n errorMessage.includes('not registered') ||\n errorMessage.includes('no provider')\n ) {\n dispatch({ type: 'not-found' })\n } else {\n dispatch({ type: 'error', error: error as Error })\n }\n return\n }\n\n // Get instance name for event subscription\n instanceNameRef.current = serviceLocator.getInstanceIdentifier(\n token as AnyInjectableType,\n args,\n )\n dispatch({ type: 'success', data: instance })\n } catch (error) {\n // Caught exceptions are treated as errors\n const err = error as Error\n const errorMessage = err.message?.toLowerCase() ?? ''\n if (\n errorMessage.includes('not found') ||\n errorMessage.includes('not registered') ||\n errorMessage.includes('no provider')\n ) {\n dispatch({ type: 'not-found' })\n } else {\n dispatch({ type: 'error', error: err })\n }\n }\n }, [serviceLocator, token, args])\n\n // Subscribe to invalidation events\n useEffect(() => {\n const eventBus = serviceLocator.getEventBus()\n let unsubscribe: (() => void) | undefined\n\n // Fetch the service\n void fetchService()\n\n // Set up subscription after we have the instance name\n const setupSubscription = () => {\n if (instanceNameRef.current) {\n unsubscribe = eventBus.on(instanceNameRef.current, 'destroy', () => {\n // Re-fetch when the service is invalidated\n void fetchService()\n })\n }\n }\n\n // Wait a tick for the instance name to be set\n const timeoutId = setTimeout(setupSubscription, 10)\n\n return () => {\n clearTimeout(timeoutId)\n unsubscribe?.()\n }\n }, [fetchService, serviceLocator])\n\n return {\n data: state.status === 'success' ? state.data : undefined,\n error: state.status === 'error' ? state.error : undefined,\n isLoading: state.status === 'loading',\n isSuccess: state.status === 'success',\n isNotFound: state.status === 'not-found',\n isError: state.status === 'error',\n refetch: fetchService,\n }\n}\n","import type {\n BoundInjectionToken,\n ClassType,\n FactoryInjectionToken,\n InjectionToken,\n InjectionTokenSchemaType,\n} from '@navios/di'\n\nimport { useCallback } from 'react'\n\nimport { useContainer } from './use-container.mjs'\n\ntype InvalidatableToken =\n | ClassType\n | InjectionToken<any, any>\n | BoundInjectionToken<any, any>\n | FactoryInjectionToken<any, any>\n\n/**\n * Hook that returns a function to invalidate a service by its token.\n *\n * When called, this will:\n * 1. Destroy the current service instance\n * 2. Trigger re-fetch in all components using useService/useSuspenseService for that token\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { data: user } = useService(UserService)\n * const invalidateUser = useInvalidate(UserService)\n *\n * const handleRefresh = () => {\n * invalidateUser() // All components using UserService will re-fetch\n * }\n *\n * return (\n * <div>\n * <span>{user?.name}</span>\n * <button onClick={handleRefresh}>Refresh</button>\n * </div>\n * )\n * }\n * ```\n */\nexport function useInvalidate<T extends InvalidatableToken>(\n token: T,\n): () => Promise<void>\n\n/**\n * Hook that returns a function to invalidate a service by its token with args.\n *\n * @example\n * ```tsx\n * function UserProfile({ userId }: { userId: string }) {\n * const args = useMemo(() => ({ userId }), [userId])\n * const { data: user } = useService(UserToken, args)\n * const invalidateUser = useInvalidate(UserToken, args)\n *\n * return (\n * <div>\n * <span>{user?.name}</span>\n * <button onClick={() => invalidateUser()}>Refresh</button>\n * </div>\n * )\n * }\n * ```\n */\nexport function useInvalidate<T, S extends InjectionTokenSchemaType>(\n token: InjectionToken<T, S>,\n args: S extends undefined ? never : unknown,\n): () => Promise<void>\n\nexport function useInvalidate(\n token: InvalidatableToken,\n args?: unknown,\n): () => Promise<void> {\n const container = useContainer()\n const serviceLocator = container.getServiceLocator()\n\n return useCallback(async () => {\n const instanceName = serviceLocator.getInstanceIdentifier(token, args)\n await serviceLocator.invalidate(instanceName)\n }, [serviceLocator, token, args])\n}\n\n/**\n * Hook that returns a function to invalidate a service instance directly.\n *\n * This is useful when you have the service instance and want to invalidate it\n * without knowing its token.\n *\n * @example\n * ```tsx\n * function UserProfile() {\n * const { data: user } = useService(UserService)\n * const invalidateInstance = useInvalidateInstance()\n *\n * const handleRefresh = () => {\n * if (user) {\n * invalidateInstance(user)\n * }\n * }\n *\n * return (\n * <div>\n * <span>{user?.name}</span>\n * <button onClick={handleRefresh}>Refresh</button>\n * </div>\n * )\n * }\n * ```\n */\nexport function useInvalidateInstance(): (instance: unknown) => Promise<void> {\n const container = useContainer()\n\n return useCallback(\n async (instance: unknown) => {\n await container.invalidate(instance)\n },\n [container],\n )\n}\n","import { useContext } from 'react'\n\nimport { ScopeContext } from '../providers/scope-provider.mjs'\n\n/**\n * Hook to get the current scope ID.\n * Returns null if not inside a ScopeProvider.\n */\nexport function useScope(): string | null {\n return useContext(ScopeContext)\n}\n\n/**\n * Hook to get the current scope ID, throwing if not inside a ScopeProvider.\n * Use this when your component requires a scope to function correctly.\n */\nexport function useScopeOrThrow(): string {\n const scope = useScope()\n if (scope === null) {\n throw new Error(\n 'useScopeOrThrow must be used within a ScopeProvider. ' +\n 'Wrap your component tree with <ScopeProvider> to create a request scope.',\n )\n }\n return scope\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@navios/di-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Oleksandr Hanzha",
|
|
6
|
+
"email": "alex@granted.name"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"directory": "packages/di-react",
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/Arilas/navios.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"@navios/di": "^0.5.0",
|
|
16
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
17
|
+
},
|
|
18
|
+
"typings": "./lib/index.d.mts",
|
|
19
|
+
"main": "./lib/index.js",
|
|
20
|
+
"module": "./lib/index.mjs",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./lib/index.d.mts",
|
|
25
|
+
"default": "./lib/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"require": {
|
|
28
|
+
"types": "./lib/index.d.ts",
|
|
29
|
+
"default": "./lib/index.js"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@navios/di": "^0.5.0",
|
|
35
|
+
"@testing-library/dom": "^10.4.1",
|
|
36
|
+
"@testing-library/react": "^16.3.0",
|
|
37
|
+
"@types/react": "^19.2.7",
|
|
38
|
+
"jsdom": "^27.3.0",
|
|
39
|
+
"react": "^19.2.3",
|
|
40
|
+
"react-dom": "^19.2.3",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"zod": "^4.1.13"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@navios/di-react",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/di-react/src",
|
|
5
|
+
"prefix": "di-react",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"projectType": "library",
|
|
8
|
+
"targets": {
|
|
9
|
+
"check": {
|
|
10
|
+
"executor": "nx:run-commands",
|
|
11
|
+
"outputs": ["{projectRoot}/dist"],
|
|
12
|
+
"inputs": ["^projectSources", "projectSources"],
|
|
13
|
+
"options": {
|
|
14
|
+
"command": ["tsc -b"],
|
|
15
|
+
"cwd": "packages/di-react"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"lint": {
|
|
19
|
+
"executor": "nx:run-commands",
|
|
20
|
+
"inputs": ["^projectSources", "project"],
|
|
21
|
+
"options": {
|
|
22
|
+
"command": "oxlint --fix",
|
|
23
|
+
"cwd": "packages/di-react"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"test:ci": {
|
|
27
|
+
"executor": "nx:run-commands",
|
|
28
|
+
"inputs": ["^projectSources", "project"],
|
|
29
|
+
"options": {
|
|
30
|
+
"command": "vitest run",
|
|
31
|
+
"cwd": "packages/di-react"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"build": {
|
|
35
|
+
"executor": "nx:run-commands",
|
|
36
|
+
"inputs": ["projectSources"],
|
|
37
|
+
"outputs": ["{projectRoot}/lib"],
|
|
38
|
+
"dependsOn": ["check", "test:ci", "lint"],
|
|
39
|
+
"options": {
|
|
40
|
+
"command": "tsup",
|
|
41
|
+
"cwd": "packages/di-react"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"publish": {
|
|
45
|
+
"executor": "nx:run-commands",
|
|
46
|
+
"dependsOn": ["build"],
|
|
47
|
+
"options": {
|
|
48
|
+
"command": "yarn npm publish --access public",
|
|
49
|
+
"cwd": "packages/di-react"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"publish:next": {
|
|
53
|
+
"executor": "nx:run-commands",
|
|
54
|
+
"dependsOn": ["build"],
|
|
55
|
+
"options": {
|
|
56
|
+
"command": "yarn npm publish --access public --tag next",
|
|
57
|
+
"cwd": "packages/di-react"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Container, Injectable, Registry } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import { renderHook } from '@testing-library/react'
|
|
4
|
+
import { createElement } from 'react'
|
|
5
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { ContainerProvider } from '../../providers/container-provider.mjs'
|
|
8
|
+
import { useContainer } from '../use-container.mjs'
|
|
9
|
+
|
|
10
|
+
describe('useContainer', () => {
|
|
11
|
+
let container: Container
|
|
12
|
+
let registry: Registry
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
registry = new Registry()
|
|
16
|
+
container = new Container(registry)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should return the container from context', () => {
|
|
20
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
21
|
+
// @ts-expect-error - container is required
|
|
22
|
+
createElement(ContainerProvider, { container }, children)
|
|
23
|
+
|
|
24
|
+
const { result } = renderHook(() => useContainer(), { wrapper })
|
|
25
|
+
|
|
26
|
+
expect(result.current).toBe(container)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should throw an error when used outside of ContainerProvider', () => {
|
|
30
|
+
expect(() => {
|
|
31
|
+
renderHook(() => useContainer())
|
|
32
|
+
}).toThrow('useContainer must be used within a ContainerProvider')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should provide access to container methods', async () => {
|
|
36
|
+
@Injectable({ registry })
|
|
37
|
+
class TestService {
|
|
38
|
+
getValue() {
|
|
39
|
+
return 'test-value'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
44
|
+
// @ts-expect-error - props are not typed
|
|
45
|
+
createElement(ContainerProvider, { container }, children)
|
|
46
|
+
|
|
47
|
+
const { result } = renderHook(() => useContainer(), { wrapper })
|
|
48
|
+
|
|
49
|
+
const service = await result.current.get(TestService)
|
|
50
|
+
expect(service.getValue()).toBe('test-value')
|
|
51
|
+
})
|
|
52
|
+
})
|