@portento/core 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luca Leone
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,475 @@
1
+ # Portento Core
2
+
3
+ [![npm version](https://badge.fury.io/js/%40portento%2Fcore.svg)](https://www.npmjs.com/package/@portento/core)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
6
+
7
+ Lightweight dependency injection framework for React and React Native with seamless MobX integration. Build scalable applications with clean architecture using decorators and IoC patterns.
8
+
9
+ ## Features
10
+
11
+ - ๐ŸŽฏ **Three-tier DI scoping** - Root, Router, and Component-level dependency management
12
+ - โšก **MobX Integration** - Automatic observer wrapping for reactive components
13
+ - ๐ŸŽจ **Decorator-based API** - Clean, declarative syntax with `@Injectable`, `@Component`, `@Router`
14
+ - ๐Ÿ”„ **Automatic Resolution** - Smart dependency injection with hierarchical fallback
15
+ - ๐Ÿงช **Testing Utilities** - Reset scopes for isolated unit tests
16
+ - ๐Ÿ“ฆ **TypeScript-first** - Full type safety and IntelliSense support
17
+ - ๐ŸŒ **Framework Agnostic** - Works with React, React Native, and Angular
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ yarn add @portento/core react mobx mobx-react tsyringe reflect-metadata
23
+ ```
24
+
25
+ Or with npm:
26
+
27
+ ```bash
28
+ npm install @portento/core react mobx mobx-react tsyringe reflect-metadata
29
+ ```
30
+
31
+ ## TypeScript Configuration
32
+
33
+ Enable decorators in your `tsconfig.json`:
34
+
35
+ ```json
36
+ {
37
+ "compilerOptions": {
38
+ "experimentalDecorators": true,
39
+ "emitDecoratorMetadata": true,
40
+ "strict": true
41
+ }
42
+ }
43
+ ```
44
+
45
+ Import `reflect-metadata` at your app entry point:
46
+
47
+ ```typescript
48
+ import 'reflect-metadata';
49
+ import { AppRegistry } from 'react-native';
50
+ import App from './App';
51
+
52
+ AppRegistry.registerComponent('MyApp', () => App);
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ### 1. Create a Service with `@Injectable`
58
+
59
+ ```typescript
60
+ import { Injectable } from '@portento/core';
61
+ import { makeAutoObservable } from 'mobx';
62
+
63
+ @Injectable({ providedIn: 'root' })
64
+ class AuthService {
65
+ public isAuthenticated = false;
66
+
67
+ constructor() {
68
+ makeAutoObservable(this);
69
+ }
70
+
71
+ login(username: string) {
72
+ this.isAuthenticated = true;
73
+ }
74
+
75
+ logout() {
76
+ this.isAuthenticated = false;
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### 2. Create a Component with `@Component`
82
+
83
+ ```typescript
84
+ import React from 'react';
85
+ import { View, Text, Button } from 'react-native';
86
+ import { Component, IoComponent } from '@portento/core';
87
+
88
+ @Component({
89
+ selector: 'home-screen',
90
+ providers: []
91
+ })
92
+ class HomeScreen implements IoComponent {
93
+ constructor(
94
+ private authService: AuthService
95
+ ) {}
96
+
97
+ render() {
98
+ return (
99
+ <View>
100
+ <Text>
101
+ {this.authService.isAuthenticated ? 'Logged In' : 'Not Logged In'}
102
+ </Text>
103
+ <Button
104
+ title="Login"
105
+ onPress={() => this.authService.login('user')}
106
+ />
107
+ </View>
108
+ );
109
+ }
110
+ }
111
+
112
+ export const HomeScreenComponent = Component.$provider(HomeScreen);
113
+ ```
114
+
115
+ ### 3. Use in Your App
116
+
117
+ ```tsx
118
+ import { HomeScreenComponent } from './HomeScreen';
119
+
120
+ export default function App() {
121
+ return <HomeScreenComponent />;
122
+ }
123
+ ```
124
+
125
+ ## Dependency Injection Scoping
126
+
127
+ Portento Core provides a three-tier scoping hierarchy for dependency management:
128
+
129
+ ### 1. Root Scope (Singleton)
130
+
131
+ Services registered at root scope are singletons shared across the entire application:
132
+
133
+ ```typescript
134
+ @Injectable({ providedIn: 'root' })
135
+ class UserStore {
136
+ public username = 'Guest';
137
+
138
+ constructor() {
139
+ makeAutoObservable(this);
140
+ }
141
+ }
142
+ ```
143
+
144
+ ### 2. Router Scope (Feature-level)
145
+
146
+ Services shared across components within the same router:
147
+
148
+ ```typescript
149
+ @Injectable()
150
+ class SharedNavStore {
151
+ public currentTab = 0;
152
+
153
+ constructor() {
154
+ makeAutoObservable(this);
155
+ }
156
+ }
157
+
158
+ @Router({
159
+ selector: 'main-nav-router',
160
+ components: [HomeScreen, ProfileScreen],
161
+ providers: [SharedNavStore]
162
+ })
163
+ class MainNavRouter implements IoComponent {
164
+ render() {
165
+ return (
166
+ <View>
167
+ <HomeScreen.$provider />
168
+ <ProfileScreen.$provider />
169
+ </View>
170
+ );
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### 3. Component Scope (Local)
176
+
177
+ Services isolated to a single component instance:
178
+
179
+ ```typescript
180
+ @Injectable()
181
+ class FormValidator {
182
+ public errors: string[] = [];
183
+
184
+ validate(value: string) {
185
+ // Validation logic
186
+ }
187
+ }
188
+
189
+ @Component({
190
+ selector: 'login-form',
191
+ providers: [FormValidator]
192
+ })
193
+ class LoginForm implements IoComponent {
194
+ constructor(private validator: FormValidator) {}
195
+
196
+ render() {
197
+ // Component implementation
198
+ }
199
+ }
200
+ ```
201
+
202
+ ## Resolution Hierarchy
203
+
204
+ Dependencies are resolved with automatic fallback:
205
+
206
+ ```
207
+ Component Scope โ†’ Router Scope โ†’ Root Scope
208
+ ```
209
+
210
+ If a dependency isn't found in the component's providers, it searches the router's providers, then falls back to root scope.
211
+
212
+ ## MobX Integration
213
+
214
+ All components are automatically wrapped with MobX's `observer()` HOC for reactive updates:
215
+
216
+ ```typescript
217
+ @Injectable({ providedIn: 'root' })
218
+ class CounterStore {
219
+ public count = 0;
220
+
221
+ constructor() {
222
+ makeAutoObservable(this);
223
+ }
224
+
225
+ increment() {
226
+ this.count++; // Component automatically re-renders
227
+ }
228
+ }
229
+
230
+ @Component({
231
+ selector: 'counter',
232
+ providers: []
233
+ })
234
+ class Counter implements IoComponent {
235
+ constructor(private store: CounterStore) {}
236
+
237
+ render() {
238
+ return (
239
+ <View>
240
+ <Text>{this.store.count}</Text>
241
+ <Button title="+" onPress={() => this.store.increment()} />
242
+ </View>
243
+ );
244
+ }
245
+ }
246
+ ```
247
+
248
+ ## Component Export Patterns
249
+
250
+ ### Pattern 1: Direct Decoration
251
+
252
+ ```typescript
253
+ @Component({
254
+ selector: 'home-screen',
255
+ providers: []
256
+ })
257
+ class HomeScreen implements IoComponent {
258
+ render() {
259
+ return <View>...</View>;
260
+ }
261
+ }
262
+
263
+ // Usage in JSX:
264
+ <HomeScreen.$provider />
265
+ ```
266
+
267
+ ### Pattern 2: Separate Class & Export (Recommended)
268
+
269
+ ```typescript
270
+ @Component({
271
+ selector: 'settings-screen',
272
+ providers: []
273
+ })
274
+ class Settings implements IoComponent {
275
+ render() {
276
+ return <View>...</View>;
277
+ }
278
+ }
279
+
280
+ export const SettingsScreen = Component.$provider(Settings);
281
+
282
+ // Usage in JSX (cleaner):
283
+ <SettingsScreen />
284
+ ```
285
+
286
+ ## Lifecycle Methods
287
+
288
+ Components support standard React lifecycle methods:
289
+
290
+ ```typescript
291
+ @Component({
292
+ selector: 'my-component',
293
+ providers: []
294
+ })
295
+ class MyComponent implements IoComponent {
296
+ componentDidMount() {
297
+ console.log('Component mounted');
298
+ }
299
+
300
+ componentDidUpdate(prevProps, prevState) {
301
+ console.log('Component updated');
302
+ }
303
+
304
+ componentWillUnmount() {
305
+ console.log('Component unmounting');
306
+ }
307
+
308
+ render() {
309
+ return <View>...</View>;
310
+ }
311
+ }
312
+ ```
313
+
314
+ ## Testing Utilities
315
+
316
+ Reset dependency instances for isolated unit tests:
317
+
318
+ ```typescript
319
+ import { resetScope, resetClass, resetAll } from '@portento/core';
320
+
321
+ // Reset all root scope instances
322
+ resetScope('root');
323
+
324
+ // Reset specific router scope
325
+ resetScope('router', 'main-nav-router');
326
+
327
+ // Reset component scope
328
+ resetScope('component', 'home-screen');
329
+
330
+ // Reset specific class (conceptually)
331
+ resetClass('AuthService');
332
+
333
+ // Reset everything
334
+ resetAll();
335
+ ```
336
+
337
+ ## API Reference
338
+
339
+ ### `@Injectable(params)`
340
+
341
+ Register a class as an injectable service.
342
+
343
+ **Parameters:**
344
+ - `providedIn?: 'root'` - Register as root-scoped singleton
345
+
346
+ ```typescript
347
+ @Injectable({ providedIn: 'root' })
348
+ class MyService {}
349
+ ```
350
+
351
+ ### `@Component(params)`
352
+
353
+ Create a React component with dependency injection.
354
+
355
+ **Parameters:**
356
+ - `selector: string` - Unique component identifier
357
+ - `providers?: Array<Class>` - Component-scoped services
358
+
359
+ ```typescript
360
+ @Component({
361
+ selector: 'my-component',
362
+ providers: [LocalService]
363
+ })
364
+ class MyComponent implements IoComponent {}
365
+ ```
366
+
367
+ ### `@Router(params)`
368
+
369
+ Create a router component that groups child components with shared dependencies.
370
+
371
+ **Parameters:**
372
+ - `selector: string` - Unique router identifier
373
+ - `components: Array<Class>` - Child components
374
+ - `providers?: Array<Class>` - Router-scoped services
375
+
376
+ ```typescript
377
+ @Router({
378
+ selector: 'main-router',
379
+ components: [ScreenA, ScreenB],
380
+ providers: [SharedStore]
381
+ })
382
+ class MainRouter implements IoComponent {}
383
+ ```
384
+
385
+ ### `IoComponent` Interface
386
+
387
+ Base interface for all components:
388
+
389
+ ```typescript
390
+ interface IoComponent<Props = any, State = any> {
391
+ state?: State;
392
+ componentDidMount?(): void;
393
+ componentDidUpdate?(prevProps: Props, prevState: State): void;
394
+ componentWillUnmount?(): void;
395
+ render(): React.ReactNode;
396
+ }
397
+ ```
398
+
399
+ ### `Controller` Type
400
+
401
+ Access React component state and methods:
402
+
403
+ ```typescript
404
+ interface Controller<Props = any, State = any> {
405
+ props: Props;
406
+ state: State;
407
+ setState: (state: Partial<State> | ((prevState: State) => State)) => void;
408
+ forceUpdate: () => void;
409
+ }
410
+ ```
411
+
412
+ Inject the controller:
413
+
414
+ ```typescript
415
+ @Component({
416
+ selector: 'stateful-component',
417
+ providers: []
418
+ })
419
+ class StatefulComponent implements IoComponent {
420
+ constructor(private controller: Controller) {}
421
+
422
+ updateState() {
423
+ this.controller.setState({ counter: 1 });
424
+ }
425
+ }
426
+ ```
427
+
428
+ ## Examples
429
+
430
+ Complete usage examples are available in the [@portento/core-examples](https://www.npmjs.com/package/@portento/core-examples) package:
431
+
432
+ ```bash
433
+ yarn add @portento/core-examples
434
+ ```
435
+
436
+ Examples include:
437
+ - **StoreExample** - MobX observable stores with different scopes
438
+ - **ScopingExample** - Dependency injection hierarchy demonstration
439
+ - **RouterScopeExample** - Shared state across router components
440
+ - **ResetExample** - Cleanup utilities for testing
441
+
442
+ ## Troubleshooting
443
+
444
+ ### Decorators not working
445
+
446
+ Ensure `experimentalDecorators` and `emitDecoratorMetadata` are enabled in `tsconfig.json`.
447
+
448
+ ### "Design:paramtypes" metadata missing
449
+
450
+ Import `reflect-metadata` at your app entry point before any other imports.
451
+
452
+ ### MobX observables not triggering re-renders
453
+
454
+ Make sure your stores use `makeAutoObservable(this)` in the constructor.
455
+
456
+ ### Dependency not found
457
+
458
+ Check the resolution order: component providers โ†’ router providers โ†’ root scope. Ensure the service is registered at the appropriate level.
459
+
460
+ ## Architecture Documentation
461
+
462
+ For detailed architecture information, see:
463
+ - [USAGE_PATTERNS.md](./docs/USAGE_PATTERNS.md) - Component patterns
464
+
465
+ ## License
466
+
467
+ MIT ยฉ Luca Leone
468
+
469
+ ## Contributing
470
+
471
+ Contributions are welcome! Please feel free to submit a Pull Request.
472
+
473
+ ## Repository
474
+
475
+ [https://github.com/luca-leone/portento-core](https://github.com/luca-leone/portento-core)
@@ -0,0 +1,93 @@
1
+ import React$1 from 'react';
2
+
3
+ type Ctor<T> = new (...args: Array<any>) => T;
4
+ interface Controller<Props = unknown, State = unknown> {
5
+ props: Props;
6
+ state: State;
7
+ setState<S = State>(state: S | ((prevState: S) => S)): void;
8
+ forceUpdate(): void;
9
+ }
10
+ type InjectableParams = {
11
+ providedIn: 'root';
12
+ };
13
+ type ComponentParams = {
14
+ selector: string;
15
+ providers?: Array<Ctor<unknown>>;
16
+ };
17
+ type RouterParams = {
18
+ selector: string;
19
+ components: Array<Ctor<unknown> | React.FC>;
20
+ providers?: Array<Ctor<unknown>>;
21
+ };
22
+ interface IoComponent<Props = unknown, State = unknown> {
23
+ state?: State;
24
+ componentDidMount?(): void;
25
+ componentDidUpdate?(prevProps: Props, prevState: State): void;
26
+ componentWillUnmount?(): void;
27
+ render(): React.ReactNode;
28
+ }
29
+ type ComponentWrapperProps = Record<string, unknown>;
30
+ type ComponentWrapperState = Record<string, unknown>;
31
+ type RouterWrapperProps = Record<string, unknown>;
32
+ type RouterWrapperState = Record<string, unknown>;
33
+
34
+ type ComponentDecoratorFunction = {
35
+ <T extends Ctor<IoComponent>>(Entity: T): T;
36
+ $provider: <T extends Ctor<IoComponent>>(Entity: T) => React$1.FC;
37
+ };
38
+ declare function Component(params: ComponentParams): ComponentDecoratorFunction;
39
+ declare namespace Component {
40
+ var $provider: <T extends Ctor<IoComponent>>(Entity: T) => React$1.FC;
41
+ }
42
+
43
+ declare function Injectable(params?: InjectableParams): ClassDecorator;
44
+
45
+ type RouterDecoratorFunction = <T extends Ctor<IoComponent>>(Entity: T) => T;
46
+ declare function Router(params: RouterParams): RouterDecoratorFunction;
47
+ declare namespace Router {
48
+ var $provider: <T extends Ctor<IoComponent>>(Entity: T) => React$1.FC;
49
+ }
50
+
51
+ /**
52
+ * Reset all dependency instances in a specific scope
53
+ * Note: Due to tsyringe limitations, this resets ALL scopes.
54
+ * Useful for cleanup after logout, between tests, or when navigating away from features.
55
+ * All providers are automatically re-registered and will be re-instantiated on next access.
56
+ *
57
+ * @param _scope - Scope type (for API compatibility, currently resets all)
58
+ * @param _selector - Scope selector (for API compatibility, currently resets all)
59
+ *
60
+ * @example
61
+ * // Clean up on logout
62
+ * resetScope('root');
63
+ *
64
+ * // Clean up when unmounting router
65
+ * resetScope('router', 'main-nav-router');
66
+ *
67
+ * // Clean up component scope
68
+ * resetScope('component', 'home-screen');
69
+ */
70
+ declare function resetScope(_scope: "root" | "router" | "component", _selector?: string): void;
71
+ /**
72
+ * Reset all dependency instances
73
+ * Note: Due to tsyringe limitations, this resets ALL instances, not just the specified class.
74
+ * Useful for targeted cleanup scenarios. All providers are automatically re-registered.
75
+ *
76
+ * @param _className - Class name (for API compatibility, currently resets all)
77
+ *
78
+ * @example
79
+ * // Conceptually reset SharedNavStore
80
+ * resetClass('SharedNavStore');
81
+ */
82
+ declare function resetClass(_className: string): void;
83
+ /**
84
+ * Reset all instances in all scopes
85
+ * Warning: This will clear all cached singletons
86
+ *
87
+ * @example
88
+ * // Clear everything (useful between tests)
89
+ * resetAll();
90
+ */
91
+ declare function resetAll(): void;
92
+
93
+ export { Component, type ComponentParams, type ComponentWrapperProps, type ComponentWrapperState, type Controller, type Ctor, Injectable, type InjectableParams, type IoComponent, Router, type RouterParams, type RouterWrapperProps, type RouterWrapperState, resetAll, resetClass, resetScope };
@@ -0,0 +1,93 @@
1
+ import React$1 from 'react';
2
+
3
+ type Ctor<T> = new (...args: Array<any>) => T;
4
+ interface Controller<Props = unknown, State = unknown> {
5
+ props: Props;
6
+ state: State;
7
+ setState<S = State>(state: S | ((prevState: S) => S)): void;
8
+ forceUpdate(): void;
9
+ }
10
+ type InjectableParams = {
11
+ providedIn: 'root';
12
+ };
13
+ type ComponentParams = {
14
+ selector: string;
15
+ providers?: Array<Ctor<unknown>>;
16
+ };
17
+ type RouterParams = {
18
+ selector: string;
19
+ components: Array<Ctor<unknown> | React.FC>;
20
+ providers?: Array<Ctor<unknown>>;
21
+ };
22
+ interface IoComponent<Props = unknown, State = unknown> {
23
+ state?: State;
24
+ componentDidMount?(): void;
25
+ componentDidUpdate?(prevProps: Props, prevState: State): void;
26
+ componentWillUnmount?(): void;
27
+ render(): React.ReactNode;
28
+ }
29
+ type ComponentWrapperProps = Record<string, unknown>;
30
+ type ComponentWrapperState = Record<string, unknown>;
31
+ type RouterWrapperProps = Record<string, unknown>;
32
+ type RouterWrapperState = Record<string, unknown>;
33
+
34
+ type ComponentDecoratorFunction = {
35
+ <T extends Ctor<IoComponent>>(Entity: T): T;
36
+ $provider: <T extends Ctor<IoComponent>>(Entity: T) => React$1.FC;
37
+ };
38
+ declare function Component(params: ComponentParams): ComponentDecoratorFunction;
39
+ declare namespace Component {
40
+ var $provider: <T extends Ctor<IoComponent>>(Entity: T) => React$1.FC;
41
+ }
42
+
43
+ declare function Injectable(params?: InjectableParams): ClassDecorator;
44
+
45
+ type RouterDecoratorFunction = <T extends Ctor<IoComponent>>(Entity: T) => T;
46
+ declare function Router(params: RouterParams): RouterDecoratorFunction;
47
+ declare namespace Router {
48
+ var $provider: <T extends Ctor<IoComponent>>(Entity: T) => React$1.FC;
49
+ }
50
+
51
+ /**
52
+ * Reset all dependency instances in a specific scope
53
+ * Note: Due to tsyringe limitations, this resets ALL scopes.
54
+ * Useful for cleanup after logout, between tests, or when navigating away from features.
55
+ * All providers are automatically re-registered and will be re-instantiated on next access.
56
+ *
57
+ * @param _scope - Scope type (for API compatibility, currently resets all)
58
+ * @param _selector - Scope selector (for API compatibility, currently resets all)
59
+ *
60
+ * @example
61
+ * // Clean up on logout
62
+ * resetScope('root');
63
+ *
64
+ * // Clean up when unmounting router
65
+ * resetScope('router', 'main-nav-router');
66
+ *
67
+ * // Clean up component scope
68
+ * resetScope('component', 'home-screen');
69
+ */
70
+ declare function resetScope(_scope: "root" | "router" | "component", _selector?: string): void;
71
+ /**
72
+ * Reset all dependency instances
73
+ * Note: Due to tsyringe limitations, this resets ALL instances, not just the specified class.
74
+ * Useful for targeted cleanup scenarios. All providers are automatically re-registered.
75
+ *
76
+ * @param _className - Class name (for API compatibility, currently resets all)
77
+ *
78
+ * @example
79
+ * // Conceptually reset SharedNavStore
80
+ * resetClass('SharedNavStore');
81
+ */
82
+ declare function resetClass(_className: string): void;
83
+ /**
84
+ * Reset all instances in all scopes
85
+ * Warning: This will clear all cached singletons
86
+ *
87
+ * @example
88
+ * // Clear everything (useful between tests)
89
+ * resetAll();
90
+ */
91
+ declare function resetAll(): void;
92
+
93
+ export { Component, type ComponentParams, type ComponentWrapperProps, type ComponentWrapperState, type Controller, type Ctor, Injectable, type InjectableParams, type IoComponent, Router, type RouterParams, type RouterWrapperProps, type RouterWrapperState, resetAll, resetClass, resetScope };