{"_id":"@appboypov/veto-mvvm","_rev":"3-ef9f3b00275c811bb704fe44e3e1df09","name":"@appboypov/veto-mvvm","dist-tags":{"latest":"1.0.0"},"versions":{"0.1.2":{"name":"@appboypov/veto-mvvm","version":"0.1.2","keywords":["viewmodel","mvvm","container","di","dependency-injection","typescript","react"],"license":"MIT","_id":"@appboypov/veto-mvvm@0.1.2","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"dist":{"shasum":"0af01d91a9490fb300f5b1085cdd7217b2958631","tarball":"https://registry.npmjs.org/@appboypov/veto-mvvm/-/veto-mvvm-0.1.2.tgz","fileCount":6,"integrity":"sha512-E7Abc1FR6ZRPulO/O/Sj8YhXoKvshorQu3k70WgUdPKvGfQn6gOmHPiATKe5u0vdFx34N5LEB+PQ6nmOsqJjiw==","signatures":[{"sig":"MEQCIH5+OCxnH9Cd3IbnwV61yCJYMsTtB1m0yCuEscoLAoFaAiA8+Us2bPrePOG6IAScVImpvm+g83EKBAGuoRmgZxKlLA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53005},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"1c64b2e962422f38d443c75cd75b31622cf66a54","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"_npmVersion":"11.3.0","description":"View/ViewModel pattern implementation and DI container for React","directories":{},"_nodeVersion":"24.1.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","react":"^18.2.0","vitest":"^1.0.0","typescript":"^5.3.0","@types/react":"^18.2.0"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/veto-mvvm_0.1.2_1764042768786_0.3475091323987676","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@appboypov/veto-mvvm","version":"0.1.3","keywords":["viewmodel","mvvm","container","di","dependency-injection","typescript","react"],"license":"MIT","_id":"@appboypov/veto-mvvm@0.1.3","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"dist":{"shasum":"fbaa51032bcfe7742b3cc559676433314c0a9e40","tarball":"https://registry.npmjs.org/@appboypov/veto-mvvm/-/veto-mvvm-0.1.3.tgz","fileCount":6,"integrity":"sha512-s55yqiYmnQgsdlMC74UHGPzQiOyd17DcIQoNnPEWDyeu9UVjkMjNPZvb5ksg4O+EDihzK5KFUkDHrrhH/VR2BA==","signatures":[{"sig":"MEUCIBS16nTYK1avPU9Cbo53WYB9pfA07WW6OSeGhCuifClHAiEAm968tOD8RoBFtXaq2ff9D416Gjetf4BuU4rGZw+njos=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53038},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"9ac8021dcd8dd3aa22225386f77cb186d60a4a9f","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"_npmVersion":"11.3.0","description":"View/ViewModel pattern implementation and DI container for React","directories":{},"_nodeVersion":"24.1.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","react":"^18.2.0","vitest":"^1.0.0","typescript":"^5.3.0","@types/react":"^18.2.0"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/veto-mvvm_0.1.3_1764042896432_0.16533698382596929","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@appboypov/veto-mvvm","version":"1.0.0","description":"MVVM pattern with BaseViewModel class, ViewModelBuilder component, and DI container for React - Flutter inspired","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"build":"tsup src/index.ts --format cjs,esm --dts","dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","typecheck":"tsc --noEmit"},"keywords":["viewmodel","mvvm","container","di","dependency-injection","typescript","react"],"license":"MIT","peerDependencies":{"react":">=18.0.0"},"devDependencies":{"@testing-library/react":"^14.3.1","@types/react":"^18.2.0","jsdom":"^27.2.0","react":"^18.2.0","react-dom":"^18.3.1","tsup":"^8.0.0","typescript":"^5.3.0","vitest":"^1.0.0"},"_id":"@appboypov/veto-mvvm@1.0.0","gitHead":"9ac8021dcd8dd3aa22225386f77cb186d60a4a9f","_nodeVersion":"24.1.0","_npmVersion":"11.3.0","dist":{"integrity":"sha512-CDIY3Y3ro4ValfVXbrHQeMP1ARRULj4lWyVt0oBZixpej8BVR9ROwulBD3U8fEBrub77jZm4BqHlxthoAefIpg==","shasum":"3300def0e71b5653c8c815469c38cdde5bb1ecd1","tarball":"https://registry.npmjs.org/@appboypov/veto-mvvm/-/veto-mvvm-1.0.0.tgz","fileCount":6,"unpackedSize":51780,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEtKlSP9rArOS+5ijhYQIAIce1LdQ/UCPu5wc16wiLnpAiEA8LRRxZjOBrac9/GVNMCyWHus0SHUZiu710xKIBooSKU="}]},"_npmUser":{"name":"appboypov","email":"info@codaveto.com"},"directories":{},"maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/veto-mvvm_1.0.0_1764634719715_0.09357911283959353"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-25T03:52:48.690Z","modified":"2025-12-02T00:18:40.109Z","0.1.2":"2025-11-25T03:52:48.996Z","0.1.3":"2025-11-25T03:54:56.636Z","1.0.0":"2025-12-02T00:18:39.904Z"},"license":"MIT","keywords":["viewmodel","mvvm","container","di","dependency-injection","typescript","react"],"description":"MVVM pattern with BaseViewModel class, ViewModelBuilder component, and DI container for React - Flutter inspired","maintainers":[{"name":"appboypov","email":"info@codaveto.com"}],"readme":"# @appboypov/veto-mvvm\n\nMVVM pattern implementation and lightweight dependency injection container for React, inspired by Flutter.\n\n## Installation\n\n```bash\nnpm install @appboypov/veto-mvvm\n```\n\n## Features\n\n- **BaseViewModel class** - Abstract class with lifecycle management and `notifyListeners()`\n- **ViewModelBuilder component** - Declarative ViewModel creation and lifecycle management\n- **Lightweight DI container** - Inspired by Flutter's get_it\n- **React 18 concurrent mode support** via `useSyncExternalStore`\n- **Zero dependencies** (peer dependency on React 18+)\n\n## Quick Start\n\n### BaseViewModel + ViewModelBuilder\n\n```tsx\nimport { BaseViewModel, ViewModelBuilder } from '@appboypov/veto-mvvm';\n\nclass CounterViewModel extends BaseViewModel {\n  private _count = 0;\n\n  get count() {\n    return this._count;\n  }\n\n  increment() {\n    this._count++;\n    this.notifyListeners(); // Triggers rebuild\n  }\n\n  async initialise(): Promise<void> {\n    // Load initial data here\n    await super.initialise(); // Call LAST\n  }\n\n  dispose(): void {\n    // Cleanup here\n    super.dispose(); // Call LAST\n  }\n}\n\nfunction CounterView() {\n  return (\n    <ViewModelBuilder\n      viewModelBuilder={() => new CounterViewModel()}\n      builder={(vm, isInitialised) => (\n        <div>\n          {isInitialised ? (\n            <>\n              <p>Count: {vm.count}</p>\n              <button onClick={() => vm.increment()}>+</button>\n            </>\n          ) : (\n            <p>Loading...</p>\n          )}\n        </div>\n      )}\n    />\n  );\n}\n```\n\n## API - BaseViewModel\n\n### `BaseViewModel<A>`\n\nAbstract base class for ViewModels with lifecycle management.\n\n```typescript\nclass MyViewModel extends BaseViewModel<MyArgs> {\n  // Your ViewModel implementation\n}\n```\n\n#### Properties\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `isInitialised` | `boolean` | Whether `initialise()` has completed |\n| `isMounted` | `boolean` | Whether component is mounted |\n| `arguments` | `A \\| undefined` | Arguments passed via `argumentBuilder` |\n\n#### Methods\n\n| Method | Description |\n|--------|-------------|\n| `initialise()` | Async initialization. Call `super.initialise()` LAST |\n| `dispose()` | Cleanup. Call `super.dispose()` LAST |\n| `notifyListeners()` | Trigger rebuild of subscribed components |\n\n#### Example\n\n```typescript\nclass UserViewModel extends BaseViewModel<{ userId: string }> {\n  user: User | null = null;\n  isLoading = false;\n\n  async initialise(): Promise<void> {\n    this.isLoading = true;\n    this.notifyListeners();\n\n    this.user = await fetchUser(this.arguments!.userId);\n    this.isLoading = false;\n\n    await super.initialise(); // Sets isInitialised = true\n  }\n\n  dispose(): void {\n    // Cleanup subscriptions, etc.\n    super.dispose();\n  }\n}\n```\n\n## API - ViewModelBuilder\n\n### `ViewModelBuilder<T, A>`\n\nComponent that creates, manages, and provides a BaseViewModel to its children.\n\n```tsx\n<ViewModelBuilder\n  viewModelBuilder={() => new MyViewModel()}\n  argumentBuilder={() => ({ userId: '123' })}\n  builder={(vm, isInitialised, child) => (\n    // Your UI here\n  )}\n  child={<StaticContent />}\n  shouldDispose={true}\n  onDispose={(vm) => console.log('Disposed')}\n/>\n```\n\n#### Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `viewModelBuilder` | `() => T` | required | Factory to create ViewModel |\n| `argumentBuilder` | `() => A` | - | Factory to provide arguments |\n| `builder` | `(vm, isInit, child?) => ReactNode` | required | Render function |\n| `child` | `ReactNode` | - | Non-rebuilding child |\n| `shouldDispose` | `boolean` | `true` | Dispose ViewModel on unmount |\n| `onDispose` | `(vm) => void` | - | Callback before dispose |\n\n#### Example with Arguments\n\n```tsx\nfunction UserProfile({ userId }: { userId: string }) {\n  return (\n    <ViewModelBuilder\n      viewModelBuilder={() => new UserViewModel()}\n      argumentBuilder={() => ({ userId })}\n      builder={(vm, isInitialised) => (\n        <div>\n          {!isInitialised ? (\n            <p>Loading...</p>\n          ) : (\n            <div>\n              <h1>{vm.user?.name}</h1>\n              <p>{vm.user?.email}</p>\n            </div>\n          )}\n        </div>\n      )}\n    />\n  );\n}\n```\n\n#### Example with Non-Rebuilding Child\n\n```tsx\n<ViewModelBuilder\n  viewModelBuilder={() => new ListViewModel()}\n  child={<ExpensiveHeader />}\n  builder={(vm, isInitialised, child) => (\n    <div>\n      {child}\n      <ul>\n        {vm.items.map(item => <li key={item.id}>{item.name}</li>)}\n      </ul>\n    </div>\n  )}\n/>\n```\n\n## API - Dependency Injection\n\n### `LocatorService`\n\nThe DI container class. Access via `locator` singleton.\n\n### Registration Methods\n\n```typescript\nimport { locator, locate } from '@appboypov/veto-mvvm';\n\n// Pre-created instance (eager)\nlocator.registerSingleton('api', new UsersApi());\n\n// Lazy instantiation (created on first access)\nlocator.registerLazySingleton('service', () => new UsersService(locate('api')));\n\n// New instance every time\nlocator.registerFactory('dto', () => new UserDto());\n```\n\n### Retrieval Methods\n\n```typescript\n// Direct access\nconst service = locator.get<UsersService>('service');\n\n// Shorthand\nconst service = locate<UsersService>('service');\n\n// React hook (memoized)\nfunction MyComponent() {\n  const service = useService<UsersService>('service');\n}\n```\n\n### Utility Methods\n\n```typescript\nlocator.isRegistered('key');  // Check if registered\nlocator.unregister('key');    // Remove registration\nlocator.reset();              // Clear all (for testing)\n```\n\n## Patterns\n\n### Service Registration at Startup\n\n```typescript\n// services/setup.ts\nexport function setupServices() {\n  locator.registerLazySingleton('usersApi', () => new UsersApi());\n  locator.registerLazySingleton('usersService', () =>\n    new UsersService(locate('usersApi'))\n  );\n}\n\n// main.tsx\nsetupServices();\nReactDOM.createRoot(root).render(<App />);\n```\n\n### ViewModel with Service Injection\n\n```typescript\nclass UsersViewModel extends BaseViewModel {\n  private usersService = locate<UsersService>('usersService');\n  users: User[] = [];\n\n  async initialise(): Promise<void> {\n    this.users = await this.usersService.getUsers();\n    await super.initialise();\n  }\n}\n```\n\n### Testing with Mocks\n\n```typescript\ndescribe('UsersViewModel', () => {\n  beforeEach(() => {\n    locator.reset();\n    locator.registerSingleton('usersService', new MockUsersService());\n  });\n\n  it('loads users on init', async () => {\n    const vm = new UsersViewModel();\n    await vm.initialise();\n    expect(vm.users).toHaveLength(2);\n  });\n});\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}