@migration-planner-ui/ioc 0.0.33 → 0.0.36

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 ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2026 Red Hat, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # @migration-planner-ui/ioc
2
+
3
+ A lightweight Inversion of Control (IoC) container solution for React applications, inspired by InversifyJS.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @migration-planner-ui/ioc --save
9
+ ```
10
+
11
+ or
12
+
13
+ ```bash
14
+ yarn add @migration-planner-ui/ioc
15
+ ```
16
+
17
+ ## Peer Dependencies
18
+
19
+ This package requires the following peer dependencies:
20
+
21
+ - `react` ^18.3.1
22
+ - `react-dom` ^18.3.1
23
+ - `react-router-dom` ^6.26.0
24
+
25
+ ## Usage
26
+
27
+ ### 1. Define Symbols for Your Dependencies
28
+
29
+ Create a symbols file to define unique identifiers for your dependencies:
30
+
31
+ ```typescript
32
+ // Symbols.ts
33
+ export const Symbols = Object.freeze({
34
+ MyService: Symbol.for("MyService"),
35
+ ApiClient: Symbol.for("ApiClient"),
36
+ });
37
+ ```
38
+
39
+ ### 2. Create and Configure the Container
40
+
41
+ Create a container instance and register your dependencies:
42
+
43
+ ```typescript
44
+ import { Container } from "@migration-planner-ui/ioc";
45
+ import { Symbols } from "./Symbols";
46
+ import { MyService } from "./services/MyService";
47
+ import { ApiClient } from "./clients/ApiClient";
48
+
49
+ function getConfiguredContainer(): Container {
50
+ const container = new Container();
51
+
52
+ // Register dependencies
53
+ container
54
+ .register(Symbols.MyService, new MyService())
55
+ .register(Symbols.ApiClient, new ApiClient());
56
+
57
+ return container;
58
+ }
59
+ ```
60
+
61
+ ### 3. Provide the Container to Your App
62
+
63
+ Wrap your application with the `Provider` component:
64
+
65
+ ```typescript
66
+ import { Provider } from "@migration-planner-ui/ioc";
67
+ import React from "react";
68
+ import ReactDOM from "react-dom/client";
69
+
70
+ const container = getConfiguredContainer();
71
+
72
+ ReactDOM.createRoot(document.getElementById("root")!).render(
73
+ <React.StrictMode>
74
+ <Provider container={container}>
75
+ <App />
76
+ </Provider>
77
+ </React.StrictMode>
78
+ );
79
+ ```
80
+
81
+ ### 4. Use Dependencies in Components
82
+
83
+ Use the `useInjection` hook to retrieve dependencies in your React components:
84
+
85
+ ```typescript
86
+ import { useInjection } from "@migration-planner-ui/ioc";
87
+ import { Symbols } from "./Symbols";
88
+ import type { MyServiceInterface } from "./services/MyService";
89
+
90
+ export const MyComponent: React.FC = () => {
91
+ const myService = useInjection<MyServiceInterface>(Symbols.MyService);
92
+
93
+ // Use the injected service
94
+ const handleClick = () => {
95
+ myService.doSomething();
96
+ };
97
+
98
+ return <button onClick={handleClick}>Click me</button>;
99
+ };
100
+ ```
101
+
102
+ ## API Reference
103
+
104
+ ### `Container`
105
+
106
+ A singleton-scoped dependency injection container.
107
+
108
+ #### Methods
109
+
110
+ ##### `register<T>(symbol: symbol, value: T): Container`
111
+
112
+ Registers a dependency with the container.
113
+
114
+ - **Parameters:**
115
+ - `symbol`: A unique symbol identifier for the dependency
116
+ - `value`: The value to register
117
+ - **Returns:** The container instance (for method chaining)
118
+
119
+ ##### `get<T>(symbol: symbol): T | undefined`
120
+
121
+ Retrieves a registered dependency from the container.
122
+
123
+ - **Parameters:**
124
+ - `symbol`: The symbol identifier of the dependency to retrieve
125
+ - **Returns:** The registered dependency value, or `undefined` if not registered.
126
+
127
+ ### `Provider`
128
+
129
+ A React context provider component that makes the container available to child components.
130
+
131
+ #### Props
132
+
133
+ - `container: Container` - The container instance to provide
134
+ - `children: React.ReactNode` - Child components
135
+
136
+ ### `useInjection<T>(symbol: symbol): T`
137
+
138
+ A React hook that retrieves a dependency from the container.
139
+
140
+ - **Parameters:**
141
+ - `symbol`: The symbol identifier of the dependency to retrieve
142
+ - **Returns:** The registered dependency value
143
+ - **Throws:** `ReferenceError` if used outside a `Provider`
144
+
145
+ ## Example
146
+
147
+ Here's a complete example demonstrating the full usage:
148
+
149
+ ```typescript
150
+ // 1. Define symbols
151
+ export const Symbols = Object.freeze({
152
+ UserApi: Symbol.for("UserApi"),
153
+ });
154
+
155
+ // 2. Create and configure container
156
+ import { Container, Provider } from "@migration-planner-ui/ioc";
157
+ import { UserApi } from "./api/UserApi";
158
+
159
+ const container = new Container();
160
+ container.register(Symbols.UserApi, new UserApi());
161
+
162
+ // 3. Provide container to app
163
+ function App() {
164
+ return (
165
+ <Provider container={container}>
166
+ <UserProfile />
167
+ </Provider>
168
+ );
169
+ }
170
+
171
+ // 4. Use dependency in component
172
+ import { useInjection } from "@migration-planner-ui/ioc";
173
+
174
+ function UserProfile() {
175
+ const userApi = useInjection(Symbols.UserApi);
176
+ const [user, setUser] = useState(null);
177
+
178
+ useEffect(() => {
179
+ userApi.getUser().then(setUser);
180
+ }, [userApi]);
181
+
182
+ return <div>{user?.name}</div>;
183
+ }
184
+ ```
185
+
186
+ ## Features
187
+
188
+ - **Simple API**: Minimal surface area with just a few core concepts
189
+ - **Type-safe**: Full TypeScript support with generics
190
+ - **React-friendly**: Designed specifically for React applications
191
+ - **Lightweight**: No external dependencies beyond React
192
+ - **Singleton scope**: All dependencies are singleton-scoped
193
+
194
+ ## Development
195
+
196
+ ### Building
197
+
198
+ ```bash
199
+ yarn build
200
+ ```
201
+
202
+ ### Code Quality
203
+
204
+ ```bash
205
+ # Check code
206
+ yarn check
207
+
208
+ # Fix code issues
209
+ yarn check:fix
210
+
211
+ # Format code
212
+ yarn format
213
+ ```
214
+
215
+ ### Cleaning
216
+
217
+ ```bash
218
+ yarn clean
219
+ ```
220
+
221
+ ## License
222
+
223
+ [Apache 2.0](LICENSE)
224
+
@@ -0,0 +1,8 @@
1
+ /** A naive, singleton-scoped dependency injection container */
2
+ export declare class Container {
3
+ #private;
4
+ constructor();
5
+ get<T>(registeredInterfaceSymbol: symbol): T;
6
+ register<T = unknown>(registeredInterfaceSymbol: symbol, value: T): Container;
7
+ }
8
+ //# sourceMappingURL=Container.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Container.d.ts","sourceRoot":"","sources":["../src/Container.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,qBAAa,SAAS;;;IAOpB,GAAG,CAAC,CAAC,EAAE,yBAAyB,EAAE,MAAM,GAAG,CAAC;IAK5C,QAAQ,CAAC,CAAC,GAAG,OAAO,EAClB,yBAAyB,EAAE,MAAM,EACjC,KAAK,EAAE,CAAC,GACP,SAAS;CAKb"}
@@ -0,0 +1,28 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _Container_registry;
13
+ /** A naive, singleton-scoped dependency injection container */
14
+ export class Container {
15
+ constructor() {
16
+ _Container_registry.set(this, void 0);
17
+ __classPrivateFieldSet(this, _Container_registry, {}, "f");
18
+ }
19
+ get(registeredInterfaceSymbol) {
20
+ const value = __classPrivateFieldGet(this, _Container_registry, "f")[registeredInterfaceSymbol];
21
+ return value;
22
+ }
23
+ register(registeredInterfaceSymbol, value) {
24
+ __classPrivateFieldGet(this, _Container_registry, "f")[registeredInterfaceSymbol] = value;
25
+ return this;
26
+ }
27
+ }
28
+ _Container_registry = new WeakMap();
@@ -0,0 +1,3 @@
1
+ import type { Container } from "./Container.js";
2
+ export declare const Context: import("react").Context<Container>;
3
+ //# sourceMappingURL=Context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Context.d.ts","sourceRoot":"","sources":["../src/Context.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,eAAO,MAAM,OAAO,oCAAwC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { createContext } from "react";
2
+ export const Context = createContext(null);
@@ -0,0 +1,5 @@
1
+ import type { Container } from "./Container.js";
2
+ export declare const Provider: React.FC<React.PropsWithChildren<{
3
+ container: Container;
4
+ }>>;
5
+ //# sourceMappingURL=Provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Provider.d.ts","sourceRoot":"","sources":["../src/Provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAC7B,KAAK,CAAC,iBAAiB,CAAC;IAAE,SAAS,EAAE,SAAS,CAAA;CAAE,CAAC,CAIlD,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Context } from "./Context.js";
3
+ export const Provider = (props) => {
4
+ const { container, children } = props;
5
+ return _jsx(Context.Provider, { value: container, children: children });
6
+ };
@@ -0,0 +1,2 @@
1
+ export declare function useInjection<T>(registeredInterfaceSymbol: symbol): T;
2
+ //# sourceMappingURL=UseInjection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UseInjection.d.ts","sourceRoot":"","sources":["../../src/hooks/UseInjection.ts"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,CAAC,CAAC,EAAE,yBAAyB,EAAE,MAAM,GAAG,CAAC,CAOpE"}
@@ -0,0 +1,9 @@
1
+ import { useContext } from "react";
2
+ import { Context } from "../Context.js";
3
+ export function useInjection(registeredInterfaceSymbol) {
4
+ const container = useContext(Context);
5
+ if (!container) {
6
+ throw new ReferenceError("useInjection must be used inside its Provider");
7
+ }
8
+ return container.get(registeredInterfaceSymbol);
9
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./Container.js";
2
+ export * from "./Context.js";
3
+ export * from "./hooks/UseInjection.js";
4
+ export * from "./Provider.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@migration-planner-ui/ioc",
3
- "version": "0.0.33",
3
+ "version": "0.0.36",
4
4
  "description": "A basic IoC solution for React apps (inspired by InversifyJs)",
5
5
  "author": "Jonathan Kilzi <jkilzi@redhat.com>",
6
6
  "license": "Apache-2.0",
@@ -46,5 +46,9 @@
46
46
  "react-dom": "^18.3.1",
47
47
  "react-router-dom": "^6.26.0"
48
48
  },
49
- "stableVersion": "1.1.1"
49
+ "files": [
50
+ "dist",
51
+ "README.md",
52
+ "LICENSE"
53
+ ]
50
54
  }
package/biome.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
3
- "extends": "//",
4
- "linter": {
5
- "enabled": true,
6
- "domains": {
7
- "react": "recommended"
8
- }
9
- }
10
- }
package/src/Container.ts DELETED
@@ -1,22 +0,0 @@
1
- /** A naive, singleton-scoped dependency injection container */
2
- export class Container {
3
- readonly #registry: Record<symbol, unknown>;
4
-
5
- constructor() {
6
- this.#registry = {};
7
- }
8
-
9
- get<T>(registeredInterfaceSymbol: symbol): T {
10
- const value = this.#registry[registeredInterfaceSymbol] as T;
11
- return value;
12
- }
13
-
14
- register<T = unknown>(
15
- registeredInterfaceSymbol: symbol,
16
- value: T,
17
- ): Container {
18
- this.#registry[registeredInterfaceSymbol] = value;
19
-
20
- return this;
21
- }
22
- }
package/src/Context.tsx DELETED
@@ -1,4 +0,0 @@
1
- import { createContext } from "react";
2
- import type { Container } from "./Container.ts";
3
-
4
- export const Context = createContext<Container | null>(null);
package/src/Provider.tsx DELETED
@@ -1,11 +0,0 @@
1
- import type React from "react";
2
- import type { PropsWithChildren } from "react";
3
- import type { Container } from "./Container.js";
4
- import { Context } from "./Context.js";
5
-
6
- export const Provider: React.FC<PropsWithChildren<{ container: Container }>> = (
7
- props,
8
- ) => {
9
- const { container, children } = props;
10
- return <Context.Provider value={container}>{children}</Context.Provider>;
11
- };
@@ -1,11 +0,0 @@
1
- import { useContext } from "react";
2
- import { Context } from "../Context.js";
3
-
4
- export function useInjection<T>(registeredInterfaceSymbol: symbol): T {
5
- const container = useContext(Context);
6
- if (!container) {
7
- throw new ReferenceError("useInjection must be used inside its Provider");
8
- }
9
-
10
- return container.get<T>(registeredInterfaceSymbol);
11
- }
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": ".tmp/tsconfig.tsbuildinfo",
4
- "composite": true,
5
- "declaration": true,
6
- "declarationMap": true,
7
- "outDir": "dist",
8
- "rootDir": "src",
9
- "target": "es6",
10
- "module": "nodenext",
11
- "moduleResolution": "nodenext",
12
- "jsx": "react-jsx"
13
- }
14
- }
File without changes