mvc-kit 2.12.5 → 2.13.1
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/BEST_PRACTICES.md +1390 -0
- package/agent-config/bin/postinstall.mjs +4 -3
- package/agent-config/bin/setup.mjs +5 -1
- package/agent-config/claude-code/agents/mvc-kit-architect.md +16 -8
- package/agent-config/claude-code/skills/guide/SKILL.md +29 -7
- package/agent-config/claude-code/skills/guide/patterns.md +12 -0
- package/agent-config/claude-code/skills/guide/recipes.md +510 -0
- package/agent-config/claude-code/skills/guide/testing.md +297 -0
- package/agent-config/claude-code/skills/review/SKILL.md +3 -13
- package/agent-config/claude-code/skills/review/checklist.md +30 -5
- package/agent-config/claude-code/skills/scaffold/SKILL.md +4 -13
- package/agent-config/lib/install-claude.mjs +90 -25
- package/dist/Channel.cjs +276 -300
- package/dist/Channel.cjs.map +1 -1
- package/dist/Channel.js +275 -299
- package/dist/Channel.js.map +1 -1
- package/dist/Collection.cjs +424 -504
- package/dist/Collection.cjs.map +1 -1
- package/dist/Collection.js +423 -503
- package/dist/Collection.js.map +1 -1
- package/dist/Controller.cjs +70 -67
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.js +69 -66
- package/dist/Controller.js.map +1 -1
- package/dist/EventBus.cjs +77 -88
- package/dist/EventBus.cjs.map +1 -1
- package/dist/EventBus.js +76 -87
- package/dist/EventBus.js.map +1 -1
- package/dist/Feed.cjs +81 -77
- package/dist/Feed.cjs.map +1 -1
- package/dist/Feed.js +80 -76
- package/dist/Feed.js.map +1 -1
- package/dist/Model.cjs +181 -207
- package/dist/Model.cjs.map +1 -1
- package/dist/Model.js +179 -205
- package/dist/Model.js.map +1 -1
- package/dist/Pagination.cjs +75 -73
- package/dist/Pagination.cjs.map +1 -1
- package/dist/Pagination.js +74 -72
- package/dist/Pagination.js.map +1 -1
- package/dist/Pending.cjs +255 -287
- package/dist/Pending.cjs.map +1 -1
- package/dist/Pending.js +253 -285
- package/dist/Pending.js.map +1 -1
- package/dist/PersistentCollection.cjs +242 -285
- package/dist/PersistentCollection.cjs.map +1 -1
- package/dist/PersistentCollection.js +241 -284
- package/dist/PersistentCollection.js.map +1 -1
- package/dist/Resource.cjs +166 -174
- package/dist/Resource.cjs.map +1 -1
- package/dist/Resource.js +164 -172
- package/dist/Resource.js.map +1 -1
- package/dist/Selection.cjs +84 -94
- package/dist/Selection.cjs.map +1 -1
- package/dist/Selection.js +83 -93
- package/dist/Selection.js.map +1 -1
- package/dist/Service.cjs +54 -55
- package/dist/Service.cjs.map +1 -1
- package/dist/Service.js +53 -54
- package/dist/Service.js.map +1 -1
- package/dist/Sorting.cjs +102 -101
- package/dist/Sorting.cjs.map +1 -1
- package/dist/Sorting.js +102 -101
- package/dist/Sorting.js.map +1 -1
- package/dist/Trackable.cjs +112 -80
- package/dist/Trackable.cjs.map +1 -1
- package/dist/Trackable.js +111 -79
- package/dist/Trackable.js.map +1 -1
- package/dist/ViewModel.cjs +528 -576
- package/dist/ViewModel.cjs.map +1 -1
- package/dist/ViewModel.js +525 -573
- package/dist/ViewModel.js.map +1 -1
- package/dist/bindPublicMethods.cjs +43 -24
- package/dist/bindPublicMethods.cjs.map +1 -1
- package/dist/bindPublicMethods.js +43 -24
- package/dist/bindPublicMethods.js.map +1 -1
- package/dist/errors.cjs +67 -68
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +68 -71
- package/dist/errors.js.map +1 -1
- package/dist/mvc-kit.cjs +44 -46
- package/dist/mvc-kit.js +5 -32
- package/dist/produceDraft.cjs +105 -95
- package/dist/produceDraft.cjs.map +1 -1
- package/dist/produceDraft.js +106 -97
- package/dist/produceDraft.js.map +1 -1
- package/dist/react/components/CardList.cjs +30 -40
- package/dist/react/components/CardList.cjs.map +1 -1
- package/dist/react/components/CardList.js +31 -41
- package/dist/react/components/CardList.js.map +1 -1
- package/dist/react/components/DataTable.cjs +146 -169
- package/dist/react/components/DataTable.cjs.map +1 -1
- package/dist/react/components/DataTable.js +147 -170
- package/dist/react/components/DataTable.js.map +1 -1
- package/dist/react/components/InfiniteScroll.cjs +51 -42
- package/dist/react/components/InfiniteScroll.cjs.map +1 -1
- package/dist/react/components/InfiniteScroll.js +52 -43
- package/dist/react/components/InfiniteScroll.js.map +1 -1
- package/dist/react/components/types.cjs +10 -6
- package/dist/react/components/types.cjs.map +1 -1
- package/dist/react/components/types.js +11 -9
- package/dist/react/components/types.js.map +1 -1
- package/dist/react/guards.cjs +10 -6
- package/dist/react/guards.cjs.map +1 -1
- package/dist/react/guards.js +11 -9
- package/dist/react/guards.js.map +1 -1
- package/dist/react/provider.cjs +23 -20
- package/dist/react/provider.cjs.map +1 -1
- package/dist/react/provider.js +23 -21
- package/dist/react/provider.js.map +1 -1
- package/dist/react/use-event-bus.cjs +24 -20
- package/dist/react/use-event-bus.cjs.map +1 -1
- package/dist/react/use-event-bus.js +24 -21
- package/dist/react/use-event-bus.js.map +1 -1
- package/dist/react/use-instance.cjs +43 -36
- package/dist/react/use-instance.cjs.map +1 -1
- package/dist/react/use-instance.js +43 -36
- package/dist/react/use-instance.js.map +1 -1
- package/dist/react/use-local.cjs +48 -64
- package/dist/react/use-local.cjs.map +1 -1
- package/dist/react/use-local.js +47 -63
- package/dist/react/use-local.js.map +1 -1
- package/dist/react/use-model.cjs +84 -98
- package/dist/react/use-model.cjs.map +1 -1
- package/dist/react/use-model.js +84 -100
- package/dist/react/use-model.js.map +1 -1
- package/dist/react/use-singleton.cjs +19 -23
- package/dist/react/use-singleton.cjs.map +1 -1
- package/dist/react/use-singleton.js +16 -20
- package/dist/react/use-singleton.js.map +1 -1
- package/dist/react/use-subscribe-only.cjs +28 -22
- package/dist/react/use-subscribe-only.cjs.map +1 -1
- package/dist/react/use-subscribe-only.js +28 -22
- package/dist/react/use-subscribe-only.js.map +1 -1
- package/dist/react/use-teardown.cjs +20 -19
- package/dist/react/use-teardown.cjs.map +1 -1
- package/dist/react/use-teardown.js +20 -19
- package/dist/react/use-teardown.js.map +1 -1
- package/dist/react-native/NativeCollection.cjs +98 -78
- package/dist/react-native/NativeCollection.cjs.map +1 -1
- package/dist/react-native/NativeCollection.js +97 -77
- package/dist/react-native/NativeCollection.js.map +1 -1
- package/dist/react-native.cjs +2 -4
- package/dist/react-native.js +1 -4
- package/dist/react.cjs +24 -26
- package/dist/react.js +1 -17
- package/dist/singleton.cjs +28 -22
- package/dist/singleton.cjs.map +1 -1
- package/dist/singleton.js +29 -26
- package/dist/singleton.js.map +1 -1
- package/dist/walkPrototypeChain.cjs +20 -12
- package/dist/walkPrototypeChain.cjs.map +1 -1
- package/dist/walkPrototypeChain.js +21 -13
- package/dist/walkPrototypeChain.js.map +1 -1
- package/dist/web/IndexedDBCollection.cjs +53 -36
- package/dist/web/IndexedDBCollection.cjs.map +1 -1
- package/dist/web/IndexedDBCollection.js +52 -35
- package/dist/web/IndexedDBCollection.js.map +1 -1
- package/dist/web/WebStorageCollection.cjs +82 -84
- package/dist/web/WebStorageCollection.cjs.map +1 -1
- package/dist/web/WebStorageCollection.js +81 -83
- package/dist/web/WebStorageCollection.js.map +1 -1
- package/dist/web/idb.cjs +107 -99
- package/dist/web/idb.cjs.map +1 -1
- package/dist/web/idb.js +108 -105
- package/dist/web/idb.js.map +1 -1
- package/dist/web.cjs +4 -6
- package/dist/web.js +1 -5
- package/dist/wrapAsyncMethods.cjs +141 -168
- package/dist/wrapAsyncMethods.cjs.map +1 -1
- package/dist/wrapAsyncMethods.js +141 -168
- package/dist/wrapAsyncMethods.js.map +1 -1
- package/examples/primitive/channel.ts +109 -0
- package/examples/primitive/collection.ts +118 -0
- package/examples/primitive/controller.ts +118 -0
- package/examples/primitive/counter.ts +108 -0
- package/examples/primitive/env.d.ts +1 -0
- package/examples/primitive/eventbus.ts +77 -0
- package/examples/primitive/feed.ts +162 -0
- package/examples/primitive/model.ts +82 -0
- package/examples/primitive/pagination.ts +91 -0
- package/examples/primitive/pending.ts +189 -0
- package/examples/primitive/persistent-collection.ts +116 -0
- package/examples/primitive/resource.ts +114 -0
- package/examples/primitive/selection.ts +96 -0
- package/examples/primitive/sorting.ts +112 -0
- package/examples/primitive/timer.ts +58 -0
- package/examples/primitive/trackable.ts +225 -0
- package/examples/primitive/tsconfig.json +20 -0
- package/examples/primitive/viewmodel-service.ts +161 -0
- package/examples/react/AuthExample/index.html +12 -0
- package/examples/react/AuthExample/src/App.tsx +29 -0
- package/examples/react/AuthExample/src/components/AdminPage.tsx +51 -0
- package/examples/react/AuthExample/src/components/AppHeader.tsx +32 -0
- package/examples/react/AuthExample/src/components/AuthGuard.tsx +50 -0
- package/examples/react/AuthExample/src/components/AuthScreen.tsx +181 -0
- package/examples/react/AuthExample/src/components/DashboardPage.tsx +41 -0
- package/examples/react/AuthExample/src/components/ProfilePage.tsx +44 -0
- package/examples/react/AuthExample/src/components/Toast.tsx +41 -0
- package/examples/react/AuthExample/src/env.d.ts +10 -0
- package/examples/react/AuthExample/src/events/AppEventBus.ts +7 -0
- package/examples/react/AuthExample/src/main.tsx +10 -0
- package/examples/react/AuthExample/src/mock/api.ts +78 -0
- package/examples/react/AuthExample/src/models/LoginFormModel.ts +19 -0
- package/examples/react/AuthExample/src/models/RegisterFormModel.ts +25 -0
- package/examples/react/AuthExample/src/services/AuthService.ts +21 -0
- package/examples/react/AuthExample/src/styles.css +445 -0
- package/examples/react/AuthExample/src/types/auth.ts +12 -0
- package/examples/react/AuthExample/src/viewmodels/AuthViewModel.ts +111 -0
- package/examples/react/AuthExample/tsconfig.json +22 -0
- package/examples/react/AuthExample/vite.config.ts +18 -0
- package/examples/react/ComplexApp/index.html +12 -0
- package/examples/react/ComplexApp/src/App.tsx +17 -0
- package/examples/react/ComplexApp/src/channels/ActivityChannel.ts +24 -0
- package/examples/react/ComplexApp/src/channels/DashboardChannel.ts +26 -0
- package/examples/react/ComplexApp/src/channels/ErrorsChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/LatencyChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/OrdersChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/RevenueChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/TrafficChannel.ts +5 -0
- package/examples/react/ComplexApp/src/channels/UsersMetricChannel.ts +5 -0
- package/examples/react/ComplexApp/src/collections/DashboardCollection.ts +6 -0
- package/examples/react/ComplexApp/src/collections/ErrorsCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/LatencyCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/OrdersCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/RevenueCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/TrafficCollection.ts +3 -0
- package/examples/react/ComplexApp/src/collections/UsersMetricCollection.ts +3 -0
- package/examples/react/ComplexApp/src/components/activity/ActivityFeed.tsx +31 -0
- package/examples/react/ComplexApp/src/components/activity/ActivityItemRow.tsx +35 -0
- package/examples/react/ComplexApp/src/components/dashboard/DashboardCard.tsx +37 -0
- package/examples/react/ComplexApp/src/components/dashboard/DashboardPage.tsx +34 -0
- package/examples/react/ComplexApp/src/components/layout/Navbar.tsx +32 -0
- package/examples/react/ComplexApp/src/components/layout/SocialFeedPanel.tsx +57 -0
- package/examples/react/ComplexApp/src/components/shared/Spinner.tsx +3 -0
- package/examples/react/ComplexApp/src/components/shared/StatusIndicator.tsx +13 -0
- package/examples/react/ComplexApp/src/components/shared/Toast.tsx +40 -0
- package/examples/react/ComplexApp/src/env.d.ts +10 -0
- package/examples/react/ComplexApp/src/events/AppEventBus.ts +7 -0
- package/examples/react/ComplexApp/src/main.tsx +10 -0
- package/examples/react/ComplexApp/src/mock-remote/MockWebSocket.ts +38 -0
- package/examples/react/ComplexApp/src/mock-remote/activity-api.ts +48 -0
- package/examples/react/ComplexApp/src/mock-remote/dashboard-generators.ts +45 -0
- package/examples/react/ComplexApp/src/mock-remote/delay.ts +18 -0
- package/examples/react/ComplexApp/src/mock-remote/social-api.ts +55 -0
- package/examples/react/ComplexApp/src/resources/ActivityResource.ts +12 -0
- package/examples/react/ComplexApp/src/resources/SocialFeedResource.ts +17 -0
- package/examples/react/ComplexApp/src/styles.css +463 -0
- package/examples/react/ComplexApp/src/types/activity.ts +8 -0
- package/examples/react/ComplexApp/src/types/dashboard.ts +5 -0
- package/examples/react/ComplexApp/src/types/social.ts +8 -0
- package/examples/react/ComplexApp/src/types/users.ts +6 -0
- package/examples/react/ComplexApp/src/viewmodels/ActivityFeedViewModel.ts +68 -0
- package/examples/react/ComplexApp/src/viewmodels/AppStateViewModel.ts +26 -0
- package/examples/react/ComplexApp/src/viewmodels/DashboardCardViewModel.ts +69 -0
- package/examples/react/ComplexApp/src/viewmodels/ErrorsCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/LatencyCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/OrdersCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/RevenueCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/SocialFeedViewModel.ts +39 -0
- package/examples/react/ComplexApp/src/viewmodels/TrafficCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/src/viewmodels/UsersMetricCardViewModel.ts +9 -0
- package/examples/react/ComplexApp/tsconfig.json +22 -0
- package/examples/react/ComplexApp/vite.config.ts +18 -0
- package/examples/react/FullApp/index.html +12 -0
- package/examples/react/FullApp/src/App.tsx +28 -0
- package/examples/react/FullApp/src/collections/ConversationsCollection.ts +4 -0
- package/examples/react/FullApp/src/collections/LocationsCollection.ts +4 -0
- package/examples/react/FullApp/src/components/auth/LoginPage.tsx +80 -0
- package/examples/react/FullApp/src/components/dashboard/DashboardPage.tsx +29 -0
- package/examples/react/FullApp/src/components/dashboard/RecentActivityCard.tsx +35 -0
- package/examples/react/FullApp/src/components/dashboard/StatsCard.tsx +19 -0
- package/examples/react/FullApp/src/components/layout/AppShell.tsx +31 -0
- package/examples/react/FullApp/src/components/layout/Header.tsx +25 -0
- package/examples/react/FullApp/src/components/layout/Sidebar.tsx +29 -0
- package/examples/react/FullApp/src/components/locations/LocationFilters.tsx +60 -0
- package/examples/react/FullApp/src/components/locations/LocationForm.tsx +112 -0
- package/examples/react/FullApp/src/components/locations/LocationProfilePage.tsx +81 -0
- package/examples/react/FullApp/src/components/locations/LocationsPage.tsx +127 -0
- package/examples/react/FullApp/src/components/messaging/ConversationList.tsx +59 -0
- package/examples/react/FullApp/src/components/messaging/MessageBubble.tsx +22 -0
- package/examples/react/FullApp/src/components/messaging/MessageThread.tsx +100 -0
- package/examples/react/FullApp/src/components/messaging/MessagingPage.tsx +52 -0
- package/examples/react/FullApp/src/components/shared/ErrorBanner.tsx +3 -0
- package/examples/react/FullApp/src/components/shared/Spinner.tsx +7 -0
- package/examples/react/FullApp/src/components/shared/Toast.tsx +41 -0
- package/examples/react/FullApp/src/components/users/UserFilters.tsx +59 -0
- package/examples/react/FullApp/src/components/users/UsersPage.tsx +80 -0
- package/examples/react/FullApp/src/components/users/UsersTable.tsx +52 -0
- package/examples/react/FullApp/src/env.d.ts +10 -0
- package/examples/react/FullApp/src/events/AppEventBus.ts +7 -0
- package/examples/react/FullApp/src/main.tsx +10 -0
- package/examples/react/FullApp/src/mock/delay.ts +21 -0
- package/examples/react/FullApp/src/mock/locations.ts +76 -0
- package/examples/react/FullApp/src/mock/messages.ts +237 -0
- package/examples/react/FullApp/src/mock/users.ts +84 -0
- package/examples/react/FullApp/src/models/LocationFormModel.ts +31 -0
- package/examples/react/FullApp/src/models/LoginFormModel.ts +19 -0
- package/examples/react/FullApp/src/resources/UsersResource.ts +12 -0
- package/examples/react/FullApp/src/services/AuthService.ts +18 -0
- package/examples/react/FullApp/src/services/LocationService.ts +23 -0
- package/examples/react/FullApp/src/services/MessageService.ts +65 -0
- package/examples/react/FullApp/src/services/UserService.ts +23 -0
- package/examples/react/FullApp/src/styles.css +767 -0
- package/examples/react/FullApp/src/types/conversation.ts +7 -0
- package/examples/react/FullApp/src/types/location.ts +12 -0
- package/examples/react/FullApp/src/types/message.ts +7 -0
- package/examples/react/FullApp/src/types/user.ts +10 -0
- package/examples/react/FullApp/src/viewmodels/AuthViewModel.ts +51 -0
- package/examples/react/FullApp/src/viewmodels/ConversationsViewModel.ts +89 -0
- package/examples/react/FullApp/src/viewmodels/DashboardViewModel.ts +56 -0
- package/examples/react/FullApp/src/viewmodels/LocationProfileViewModel.ts +81 -0
- package/examples/react/FullApp/src/viewmodels/LocationsViewModel.ts +113 -0
- package/examples/react/FullApp/src/viewmodels/MessageThreadViewModel.ts +83 -0
- package/examples/react/FullApp/src/viewmodels/UsersViewModel.ts +88 -0
- package/examples/react/FullApp/tsconfig.json +22 -0
- package/examples/react/FullApp/vite.config.ts +18 -0
- package/examples/react/WorkerApp/index.html +12 -0
- package/examples/react/WorkerApp/src/App.tsx +24 -0
- package/examples/react/WorkerApp/src/channels/MessagingChannel.ts +46 -0
- package/examples/react/WorkerApp/src/channels/WorkerStatusChannel.ts +35 -0
- package/examples/react/WorkerApp/src/components/auth/LoginPage.tsx +60 -0
- package/examples/react/WorkerApp/src/components/layout/AppShell.tsx +31 -0
- package/examples/react/WorkerApp/src/components/layout/Header.tsx +23 -0
- package/examples/react/WorkerApp/src/components/layout/Sidebar.tsx +28 -0
- package/examples/react/WorkerApp/src/components/messaging/ComposeBar.tsx +33 -0
- package/examples/react/WorkerApp/src/components/messaging/ConversationList.tsx +59 -0
- package/examples/react/WorkerApp/src/components/messaging/MessageBubble.tsx +45 -0
- package/examples/react/WorkerApp/src/components/messaging/MessageThread.tsx +93 -0
- package/examples/react/WorkerApp/src/components/messaging/MessagingPage.tsx +53 -0
- package/examples/react/WorkerApp/src/components/shared/ErrorBanner.tsx +3 -0
- package/examples/react/WorkerApp/src/components/shared/PendingBanner.tsx +37 -0
- package/examples/react/WorkerApp/src/components/shared/Spinner.tsx +7 -0
- package/examples/react/WorkerApp/src/components/shared/Toast.tsx +41 -0
- package/examples/react/WorkerApp/src/components/shift/ShiftPage.tsx +98 -0
- package/examples/react/WorkerApp/src/components/shift/ShiftTimer.tsx +24 -0
- package/examples/react/WorkerApp/src/components/shift/SiteSelector.tsx +27 -0
- package/examples/react/WorkerApp/src/components/sites/SiteFilters.tsx +61 -0
- package/examples/react/WorkerApp/src/components/sites/SitesPage.tsx +102 -0
- package/examples/react/WorkerApp/src/env.d.ts +10 -0
- package/examples/react/WorkerApp/src/events/AppEventBus.ts +7 -0
- package/examples/react/WorkerApp/src/main.tsx +10 -0
- package/examples/react/WorkerApp/src/mock/MockWebSocket.ts +38 -0
- package/examples/react/WorkerApp/src/mock/delay.ts +31 -0
- package/examples/react/WorkerApp/src/mock/messages.ts +120 -0
- package/examples/react/WorkerApp/src/mock/shifts.ts +57 -0
- package/examples/react/WorkerApp/src/mock/sites.ts +14 -0
- package/examples/react/WorkerApp/src/mock/workers.ts +12 -0
- package/examples/react/WorkerApp/src/models/ComposeMessageModel.ts +17 -0
- package/examples/react/WorkerApp/src/resources/ConversationsResource.ts +10 -0
- package/examples/react/WorkerApp/src/resources/MessagesResource.ts +32 -0
- package/examples/react/WorkerApp/src/resources/ShiftResource.ts +73 -0
- package/examples/react/WorkerApp/src/resources/SitesResource.ts +11 -0
- package/examples/react/WorkerApp/src/resources/WorkersResource.ts +11 -0
- package/examples/react/WorkerApp/src/styles.css +756 -0
- package/examples/react/WorkerApp/src/types/conversation.ts +7 -0
- package/examples/react/WorkerApp/src/types/message.ts +7 -0
- package/examples/react/WorkerApp/src/types/shift.ts +13 -0
- package/examples/react/WorkerApp/src/types/site.ts +8 -0
- package/examples/react/WorkerApp/src/types/worker.ts +8 -0
- package/examples/react/WorkerApp/src/viewmodels/AuthViewModel.ts +41 -0
- package/examples/react/WorkerApp/src/viewmodels/ConversationsViewModel.ts +83 -0
- package/examples/react/WorkerApp/src/viewmodels/MessageThreadViewModel.ts +113 -0
- package/examples/react/WorkerApp/src/viewmodels/ShiftViewModel.ts +147 -0
- package/examples/react/WorkerApp/src/viewmodels/SitesViewModel.ts +82 -0
- package/examples/react/WorkerApp/tsconfig.json +22 -0
- package/examples/react/WorkerApp/vite.config.ts +18 -0
- package/package.json +11 -9
- package/src/Pending.test.ts +1 -2
- package/src/Sorting.test.ts +1 -1
- package/src/produceDraft.test.ts +3 -3
- package/src/react/components/CardList.test.tsx +1 -1
- package/src/react/components/DataTable.test.tsx +1 -1
- package/src/react/components/InfiniteScroll.test.tsx +5 -5
- package/dist/mvc-kit.cjs.map +0 -1
- package/dist/mvc-kit.js.map +0 -1
- package/dist/react-native.cjs.map +0 -1
- package/dist/react-native.js.map +0 -1
- package/dist/react.cjs.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/web.cjs.map +0 -1
- package/dist/web.js.map +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ViewModel, singleton } from 'mvc-kit';
|
|
2
|
+
import type { WorkerState } from '../types/worker';
|
|
3
|
+
import { MOCK_WORKERS } from '../mock/workers';
|
|
4
|
+
import { mockFetch } from '../mock/delay';
|
|
5
|
+
import { AppEventBus } from '../events/AppEventBus';
|
|
6
|
+
|
|
7
|
+
interface AuthState {
|
|
8
|
+
worker: WorkerState | null;
|
|
9
|
+
isAuthenticated: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class AuthViewModel extends ViewModel<AuthState> {
|
|
13
|
+
static DEFAULT_STATE: AuthState = { worker: null, isAuthenticated: false };
|
|
14
|
+
|
|
15
|
+
// --- Private fields ---
|
|
16
|
+
private bus = singleton(AppEventBus);
|
|
17
|
+
|
|
18
|
+
// --- Computed getters ---
|
|
19
|
+
get displayName(): string {
|
|
20
|
+
const { worker } = this.state;
|
|
21
|
+
return worker ? worker.name : '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get initials(): string {
|
|
25
|
+
const { worker } = this.state;
|
|
26
|
+
return worker ? worker.avatar : '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// --- Actions ---
|
|
30
|
+
async login(email: string) {
|
|
31
|
+
const worker = MOCK_WORKERS.find(w => w.email === email) ?? MOCK_WORKERS[0]!;
|
|
32
|
+
await mockFetch(null, 300);
|
|
33
|
+
this.set({ worker, isAuthenticated: true });
|
|
34
|
+
this.bus.emit('toast:show', { message: `Welcome, ${worker.name}!`, severity: 'success' });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
logout() {
|
|
38
|
+
this.set({ worker: null, isAuthenticated: false });
|
|
39
|
+
this.bus.emit('toast:show', { message: 'Logged out', severity: 'info' });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ViewModel, singleton } from 'mvc-kit';
|
|
2
|
+
import type { ConversationState } from '../types/conversation';
|
|
3
|
+
import { ConversationsResource } from '../resources/ConversationsResource';
|
|
4
|
+
import { WorkersResource } from '../resources/WorkersResource';
|
|
5
|
+
import { AuthViewModel } from './AuthViewModel';
|
|
6
|
+
|
|
7
|
+
export interface ConversationDisplay extends ConversationState {
|
|
8
|
+
displayName: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ConversationsViewState {
|
|
12
|
+
search: string;
|
|
13
|
+
selectedId: string | null;
|
|
14
|
+
currentWorkerId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ConversationsViewModel extends ViewModel<ConversationsViewState> {
|
|
18
|
+
// --- Private fields ---
|
|
19
|
+
private resource = singleton(ConversationsResource);
|
|
20
|
+
private workers = singleton(WorkersResource);
|
|
21
|
+
|
|
22
|
+
// --- Computed getters ---
|
|
23
|
+
get items(): ConversationState[] {
|
|
24
|
+
return this.resource.items as ConversationState[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get filtered(): ConversationDisplay[] {
|
|
28
|
+
const { search, currentWorkerId } = this.state;
|
|
29
|
+
let result = this.items;
|
|
30
|
+
|
|
31
|
+
if (search) {
|
|
32
|
+
const q = search.toLowerCase();
|
|
33
|
+
result = result.filter(conv =>
|
|
34
|
+
conv.participantIds.some(id => {
|
|
35
|
+
const worker = this.workers.get(id);
|
|
36
|
+
return worker ? worker.name.toLowerCase().includes(q) : false;
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return result.map(conv => ({
|
|
42
|
+
...conv,
|
|
43
|
+
displayName: conv.participantIds
|
|
44
|
+
.filter(id => id !== currentWorkerId)
|
|
45
|
+
.map(id => {
|
|
46
|
+
const worker = this.workers.get(id);
|
|
47
|
+
return worker ? worker.name : 'Unknown';
|
|
48
|
+
})
|
|
49
|
+
.join(', '),
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get selected(): ConversationDisplay | undefined {
|
|
54
|
+
return this.filtered.find(c => c.id === this.state.selectedId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get totalUnread(): number {
|
|
58
|
+
return this.items.reduce((sum, c) => sum + c.unreadCount, 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --- Lifecycle ---
|
|
62
|
+
protected onInit() {
|
|
63
|
+
const auth = singleton(AuthViewModel);
|
|
64
|
+
const currentWorkerId = auth.state.worker?.id ?? '';
|
|
65
|
+
this.set({ currentWorkerId });
|
|
66
|
+
|
|
67
|
+
if (this.workers.length === 0) this.workers.loadAll();
|
|
68
|
+
if (this.resource.length === 0) this.load();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- Actions ---
|
|
72
|
+
async load() {
|
|
73
|
+
const { currentWorkerId } = this.state;
|
|
74
|
+
await this.resource.loadAll(currentWorkerId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
selectConversation(id: string) {
|
|
78
|
+
this.set({ selectedId: id });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Setters ---
|
|
82
|
+
setSearch(search: string) { this.set({ search }); }
|
|
83
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { ViewModel, singleton, Feed } from 'mvc-kit';
|
|
2
|
+
import type { MessageState } from '../types/message';
|
|
3
|
+
import { MessagesResource } from '../resources/MessagesResource';
|
|
4
|
+
import { MessagingChannel } from '../channels/MessagingChannel';
|
|
5
|
+
import { WorkersResource } from '../resources/WorkersResource';
|
|
6
|
+
import { AuthViewModel } from './AuthViewModel';
|
|
7
|
+
|
|
8
|
+
interface ThreadState {}
|
|
9
|
+
|
|
10
|
+
interface ThreadEvents {
|
|
11
|
+
messageSent: { conversationId: string };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class MessageThreadViewModel extends ViewModel<ThreadState, ThreadEvents> {
|
|
15
|
+
// --- Private fields ---
|
|
16
|
+
private resource = singleton(MessagesResource);
|
|
17
|
+
private channel = singleton(MessagingChannel);
|
|
18
|
+
private workers = singleton(WorkersResource);
|
|
19
|
+
private _loadController: AbortController | null = null;
|
|
20
|
+
private _conversationId: string = '';
|
|
21
|
+
|
|
22
|
+
readonly feed = new Feed<MessageState>();
|
|
23
|
+
|
|
24
|
+
// --- Computed getters ---
|
|
25
|
+
get sortedMessages(): MessageState[] {
|
|
26
|
+
return [...this.feed.items].sort((a, b) => a.sentAt - b.sentAt);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get pending() {
|
|
30
|
+
return this.resource.pending;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get hasPendingMessages(): boolean {
|
|
34
|
+
return this.resource.pending.hasPending;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get hasFailedMessages(): boolean {
|
|
38
|
+
return this.resource.pending.hasFailed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// --- Lifecycle ---
|
|
42
|
+
protected onInit() {
|
|
43
|
+
this.listenTo(this.channel, 'newMessage', (msg) => {
|
|
44
|
+
if (msg.conversationId === this._conversationId) {
|
|
45
|
+
this.feed.push(msg);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
this.channel.connect();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --- Actions ---
|
|
52
|
+
async loadConversation(conversationId: string) {
|
|
53
|
+
this._loadController?.abort();
|
|
54
|
+
this._loadController = new AbortController();
|
|
55
|
+
this._conversationId = conversationId;
|
|
56
|
+
|
|
57
|
+
this.feed.reset();
|
|
58
|
+
|
|
59
|
+
const page = await this.resource.loadMessages(
|
|
60
|
+
conversationId,
|
|
61
|
+
AbortSignal.any([this.disposeSignal, this._loadController.signal]),
|
|
62
|
+
);
|
|
63
|
+
this.feed.appendPage(page);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async loadOlderMessages() {
|
|
67
|
+
if (!this.feed.hasMore || !this._conversationId) return;
|
|
68
|
+
|
|
69
|
+
const page = await this.resource.loadMessages(
|
|
70
|
+
this._conversationId,
|
|
71
|
+
this.disposeSignal,
|
|
72
|
+
{ cursor: this.feed.cursor },
|
|
73
|
+
);
|
|
74
|
+
this.feed.appendPage(page);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
sendMessage(conversationId: string, text: string, recipientName: string) {
|
|
78
|
+
const trimmed = text.trim();
|
|
79
|
+
if (!trimmed) return;
|
|
80
|
+
|
|
81
|
+
const auth = singleton(AuthViewModel);
|
|
82
|
+
const senderId = auth.state.worker?.id ?? '';
|
|
83
|
+
const tempId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
84
|
+
|
|
85
|
+
// Optimistic: add to feed immediately
|
|
86
|
+
const optimisticMsg: MessageState = {
|
|
87
|
+
id: tempId,
|
|
88
|
+
conversationId,
|
|
89
|
+
senderId,
|
|
90
|
+
text: trimmed,
|
|
91
|
+
sentAt: Date.now(),
|
|
92
|
+
};
|
|
93
|
+
this.feed.push(optimisticMsg);
|
|
94
|
+
|
|
95
|
+
// Enqueue via resource Pending for retry — meta provides UI context
|
|
96
|
+
this.resource.enqueueSend(tempId, conversationId, senderId, trimmed, recipientName);
|
|
97
|
+
|
|
98
|
+
this.emit('messageSent', { conversationId });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
retryMessage(id: string) {
|
|
102
|
+
this.resource.pending.retry(id);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
dismissMessage(id: string) {
|
|
106
|
+
this.resource.pending.dismiss(id);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getSenderName(senderId: string): string {
|
|
110
|
+
const worker = this.workers.get(senderId);
|
|
111
|
+
return worker ? worker.name : 'Unknown';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ViewModel, singleton } from 'mvc-kit';
|
|
2
|
+
import type { ShiftState } from '../types/shift';
|
|
3
|
+
import type { SiteState } from '../types/site';
|
|
4
|
+
import { ShiftResource } from '../resources/ShiftResource';
|
|
5
|
+
import { SitesResource } from '../resources/SitesResource';
|
|
6
|
+
import { AuthViewModel } from './AuthViewModel';
|
|
7
|
+
|
|
8
|
+
interface ShiftViewState {
|
|
9
|
+
selectedSiteId: string | null;
|
|
10
|
+
now: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ShiftViewModel extends ViewModel<ShiftViewState> {
|
|
14
|
+
// --- Private fields ---
|
|
15
|
+
private shiftResource = singleton(ShiftResource);
|
|
16
|
+
private sites = singleton(SitesResource);
|
|
17
|
+
private auth = singleton(AuthViewModel);
|
|
18
|
+
private _timer: ReturnType<typeof setInterval> | null = null;
|
|
19
|
+
|
|
20
|
+
// --- Computed getters ---
|
|
21
|
+
get currentShift(): ShiftState | null {
|
|
22
|
+
const items = this.shiftResource.items as ShiftState[];
|
|
23
|
+
return items.length > 0 ? items[0]! : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get isOnShift(): boolean {
|
|
27
|
+
return this.currentShift !== null && this.currentShift.clockOut === null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get isOnBreak(): boolean {
|
|
31
|
+
const shift = this.currentShift;
|
|
32
|
+
if (!shift) return false;
|
|
33
|
+
return shift.breaks.some(b => b.end === null);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get shiftDuration(): number {
|
|
37
|
+
const shift = this.currentShift;
|
|
38
|
+
if (!shift) return 0;
|
|
39
|
+
const end = shift.clockOut ?? this.state.now;
|
|
40
|
+
return end - shift.clockIn;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get breakDuration(): number {
|
|
44
|
+
const shift = this.currentShift;
|
|
45
|
+
if (!shift) return 0;
|
|
46
|
+
return shift.breaks.reduce((sum, b) => {
|
|
47
|
+
const end = b.end ?? this.state.now;
|
|
48
|
+
return sum + (end - b.start);
|
|
49
|
+
}, 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get formattedShiftTime(): string {
|
|
53
|
+
return formatDuration(this.shiftDuration);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get formattedBreakTime(): string {
|
|
57
|
+
return formatDuration(this.breakDuration);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get currentSiteName(): string {
|
|
61
|
+
const shift = this.currentShift;
|
|
62
|
+
if (!shift) return '';
|
|
63
|
+
const site = this.sites.get(shift.siteId) as SiteState | undefined;
|
|
64
|
+
return site?.name ?? '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get availableSites(): SiteState[] {
|
|
68
|
+
return (this.sites.items as SiteState[]).filter(s => s.status === 'active');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get pending() {
|
|
72
|
+
return this.shiftResource.pending;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get hasPendingOps(): boolean {
|
|
76
|
+
return this.shiftResource.pending.hasPending;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get hasFailedOps(): boolean {
|
|
80
|
+
return this.shiftResource.pending.hasFailed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// --- Lifecycle ---
|
|
84
|
+
protected onInit() {
|
|
85
|
+
const workerId = this.auth.state.worker?.id;
|
|
86
|
+
if (workerId && this.shiftResource.length === 0) this.load();
|
|
87
|
+
if (this.sites.length === 0) this.sites.loadAll();
|
|
88
|
+
|
|
89
|
+
this._timer = setInterval(() => {
|
|
90
|
+
this.set({ now: Date.now() });
|
|
91
|
+
}, 1000);
|
|
92
|
+
this.addCleanup(() => {
|
|
93
|
+
if (this._timer) clearInterval(this._timer);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- Actions ---
|
|
98
|
+
async load() {
|
|
99
|
+
const workerId = this.auth.state.worker?.id;
|
|
100
|
+
if (!workerId) return;
|
|
101
|
+
await this.shiftResource.loadCurrent(workerId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
clockIn() {
|
|
105
|
+
const workerId = this.auth.state.worker?.id;
|
|
106
|
+
const siteId = this.state.selectedSiteId;
|
|
107
|
+
if (!workerId || !siteId) return;
|
|
108
|
+
this.shiftResource.clockIn(workerId, siteId, this.currentSiteName || siteId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
clockOut() {
|
|
112
|
+
const shift = this.currentShift;
|
|
113
|
+
if (!shift) return;
|
|
114
|
+
this.shiftResource.clockOut(shift.id, this.currentSiteName);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
startBreak() {
|
|
118
|
+
const shift = this.currentShift;
|
|
119
|
+
if (!shift) return;
|
|
120
|
+
this.shiftResource.startBreak(shift.id, this.currentSiteName);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
endBreak() {
|
|
124
|
+
const shift = this.currentShift;
|
|
125
|
+
if (!shift) return;
|
|
126
|
+
this.shiftResource.endBreak(shift.id, this.currentSiteName);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
retryAll() {
|
|
130
|
+
this.shiftResource.pending.retryAll();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
dismissAll() {
|
|
134
|
+
this.shiftResource.pending.dismissAll();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --- Setters ---
|
|
138
|
+
selectSite(siteId: string) { this.set({ selectedSiteId: siteId }); }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function formatDuration(ms: number): string {
|
|
142
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
143
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
144
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
145
|
+
const seconds = totalSeconds % 60;
|
|
146
|
+
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
147
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ViewModel, singleton, Sorting, Pagination, Selection } from 'mvc-kit';
|
|
2
|
+
import type { SiteState } from '../types/site';
|
|
3
|
+
import { SitesResource } from '../resources/SitesResource';
|
|
4
|
+
|
|
5
|
+
interface SitesViewState {
|
|
6
|
+
search: string;
|
|
7
|
+
typeFilter: 'all' | SiteState['type'];
|
|
8
|
+
statusFilter: 'all' | SiteState['status'];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class SitesViewModel extends ViewModel<SitesViewState> {
|
|
12
|
+
// --- Private fields ---
|
|
13
|
+
private resource = singleton(SitesResource);
|
|
14
|
+
|
|
15
|
+
readonly sorting = new Sorting<SiteState>({ sorts: [{ key: 'name', direction: 'asc' }] });
|
|
16
|
+
readonly pagination = new Pagination({ pageSize: 10 });
|
|
17
|
+
readonly selection = new Selection<string>();
|
|
18
|
+
|
|
19
|
+
// --- Computed getters ---
|
|
20
|
+
get items(): SiteState[] {
|
|
21
|
+
return this.resource.items as SiteState[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get filtered(): SiteState[] {
|
|
25
|
+
const { search, typeFilter, statusFilter } = this.state;
|
|
26
|
+
let result = this.items;
|
|
27
|
+
|
|
28
|
+
if (search) {
|
|
29
|
+
const q = search.toLowerCase();
|
|
30
|
+
result = result.filter(site =>
|
|
31
|
+
site.name.toLowerCase().includes(q) ||
|
|
32
|
+
site.address.toLowerCase().includes(q),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (typeFilter !== 'all') {
|
|
36
|
+
result = result.filter(site => site.type === typeFilter);
|
|
37
|
+
}
|
|
38
|
+
if (statusFilter !== 'all') {
|
|
39
|
+
result = result.filter(site => site.status === statusFilter);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get sorted(): SiteState[] {
|
|
45
|
+
return this.sorting.apply(this.filtered);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get paged(): SiteState[] {
|
|
49
|
+
return this.pagination.apply(this.sorted);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get total(): number {
|
|
53
|
+
return this.items.length;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get filteredCount(): number {
|
|
57
|
+
return this.filtered.length;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get hasResults(): boolean {
|
|
61
|
+
return this.filtered.length > 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get selectedItems(): SiteState[] {
|
|
65
|
+
return this.selection.selectedFrom(this.filtered, site => site.id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- Lifecycle ---
|
|
69
|
+
protected onInit() {
|
|
70
|
+
if (this.resource.length === 0) this.load();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// --- Actions ---
|
|
74
|
+
async load() {
|
|
75
|
+
await this.resource.loadAll();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- Setters ---
|
|
79
|
+
setSearch(search: string) { this.set({ search }); this.pagination.reset(); }
|
|
80
|
+
setTypeFilter(typeFilter: SitesViewState['typeFilter']) { this.set({ typeFilter }); this.pagination.reset(); }
|
|
81
|
+
setStatusFilter(statusFilter: SitesViewState['statusFilter']) { this.set({ statusFilter }); this.pagination.reset(); }
|
|
82
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"erasableSyntaxOnly": false,
|
|
13
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
14
|
+
"types": [],
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"mvc-kit": ["../../../src/index.ts"],
|
|
18
|
+
"mvc-kit/react": ["../../../src/react/index.ts"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["src"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
root: import.meta.dirname,
|
|
6
|
+
define: {
|
|
7
|
+
__MVC_KIT_DEV__: true,
|
|
8
|
+
},
|
|
9
|
+
resolve: {
|
|
10
|
+
alias: {
|
|
11
|
+
'mvc-kit/react': resolve(import.meta.dirname, '../../../src/react/index.ts'),
|
|
12
|
+
'mvc-kit': resolve(import.meta.dirname, '../../../src/index.ts'),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
server: {
|
|
16
|
+
port: 3002,
|
|
17
|
+
},
|
|
18
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mvc-kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"description": "Zero-magic, class-based reactive ViewModel library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/mvc-kit.cjs",
|
|
@@ -64,9 +64,11 @@
|
|
|
64
64
|
"files": [
|
|
65
65
|
"./mvc-kit-logo.jpg",
|
|
66
66
|
"dist",
|
|
67
|
+
"examples",
|
|
67
68
|
"src",
|
|
68
69
|
"agent-config",
|
|
69
|
-
"README.md"
|
|
70
|
+
"README.md",
|
|
71
|
+
"BEST_PRACTICES.md"
|
|
70
72
|
],
|
|
71
73
|
"bin": {
|
|
72
74
|
"mvc-kit-setup": "./agent-config/bin/setup.mjs"
|
|
@@ -81,16 +83,16 @@
|
|
|
81
83
|
},
|
|
82
84
|
"devDependencies": {
|
|
83
85
|
"@testing-library/react": "^16.0.0",
|
|
84
|
-
"@types/react": "^
|
|
85
|
-
"esbuild": "^0.
|
|
86
|
+
"@types/react": "^19.2.14",
|
|
87
|
+
"esbuild": "^0.28.0",
|
|
86
88
|
"fake-indexeddb": "^6.2.5",
|
|
87
|
-
"jsdom": "^
|
|
89
|
+
"jsdom": "^29.0.2",
|
|
88
90
|
"msw": "^2.12.10",
|
|
89
|
-
"react": "^
|
|
90
|
-
"react-dom": "^
|
|
91
|
+
"react": "^19.2.5",
|
|
92
|
+
"react-dom": "^19.2.5",
|
|
91
93
|
"react-router-dom": "^7.0.0",
|
|
92
|
-
"typescript": "~
|
|
93
|
-
"vite": "^
|
|
94
|
+
"typescript": "~6.0.2",
|
|
95
|
+
"vite": "^8.0.8",
|
|
94
96
|
"vitest": "^4.0.18"
|
|
95
97
|
}
|
|
96
98
|
}
|
package/src/Pending.test.ts
CHANGED
|
@@ -1510,8 +1510,7 @@ describe('Pending', () => {
|
|
|
1510
1510
|
|
|
1511
1511
|
it('supersede replaces meta', async () => {
|
|
1512
1512
|
const p = new Pending<string, TestMeta>();
|
|
1513
|
-
|
|
1514
|
-
const promise1 = new Promise<void>(r => { resolve1 = r; });
|
|
1513
|
+
const promise1 = new Promise<void>(() => {});
|
|
1515
1514
|
|
|
1516
1515
|
p.enqueue('a', 'send', () => promise1, { label: 'first', priority: 1 });
|
|
1517
1516
|
await vi.advanceTimersByTimeAsync(0);
|
package/src/Sorting.test.ts
CHANGED
package/src/produceDraft.test.ts
CHANGED
|
@@ -143,7 +143,7 @@ describe('produceDraft', () => {
|
|
|
143
143
|
// ── Original immutability ───────────────────────────────────────
|
|
144
144
|
|
|
145
145
|
it('never mutates original state', () => {
|
|
146
|
-
const state = Object.freeze({ count: 0, name: 'test' });
|
|
146
|
+
const state = Object.freeze<{ count: number; name: string }>({ count: 0, name: 'test' });
|
|
147
147
|
const result = produceDraft(state, (d) => {
|
|
148
148
|
d.count = 5;
|
|
149
149
|
d.name = 'updated';
|
|
@@ -154,8 +154,8 @@ describe('produceDraft', () => {
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
it('never mutates original nested objects', () => {
|
|
157
|
-
const config =
|
|
158
|
-
const state = Object.freeze({ config });
|
|
157
|
+
const config = { theme: 'dark', size: 14 };
|
|
158
|
+
const state = Object.freeze<{ config: { theme: string; size: number } }>({ config });
|
|
159
159
|
const result = produceDraft(state, (d) => {
|
|
160
160
|
d.config.theme = 'light';
|
|
161
161
|
});
|