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.
Files changed (382) hide show
  1. package/BEST_PRACTICES.md +1390 -0
  2. package/agent-config/bin/postinstall.mjs +4 -3
  3. package/agent-config/bin/setup.mjs +5 -1
  4. package/agent-config/claude-code/agents/mvc-kit-architect.md +16 -8
  5. package/agent-config/claude-code/skills/guide/SKILL.md +29 -7
  6. package/agent-config/claude-code/skills/guide/patterns.md +12 -0
  7. package/agent-config/claude-code/skills/guide/recipes.md +510 -0
  8. package/agent-config/claude-code/skills/guide/testing.md +297 -0
  9. package/agent-config/claude-code/skills/review/SKILL.md +3 -13
  10. package/agent-config/claude-code/skills/review/checklist.md +30 -5
  11. package/agent-config/claude-code/skills/scaffold/SKILL.md +4 -13
  12. package/agent-config/lib/install-claude.mjs +90 -25
  13. package/dist/Channel.cjs +276 -300
  14. package/dist/Channel.cjs.map +1 -1
  15. package/dist/Channel.js +275 -299
  16. package/dist/Channel.js.map +1 -1
  17. package/dist/Collection.cjs +424 -504
  18. package/dist/Collection.cjs.map +1 -1
  19. package/dist/Collection.js +423 -503
  20. package/dist/Collection.js.map +1 -1
  21. package/dist/Controller.cjs +70 -67
  22. package/dist/Controller.cjs.map +1 -1
  23. package/dist/Controller.js +69 -66
  24. package/dist/Controller.js.map +1 -1
  25. package/dist/EventBus.cjs +77 -88
  26. package/dist/EventBus.cjs.map +1 -1
  27. package/dist/EventBus.js +76 -87
  28. package/dist/EventBus.js.map +1 -1
  29. package/dist/Feed.cjs +81 -77
  30. package/dist/Feed.cjs.map +1 -1
  31. package/dist/Feed.js +80 -76
  32. package/dist/Feed.js.map +1 -1
  33. package/dist/Model.cjs +181 -207
  34. package/dist/Model.cjs.map +1 -1
  35. package/dist/Model.js +179 -205
  36. package/dist/Model.js.map +1 -1
  37. package/dist/Pagination.cjs +75 -73
  38. package/dist/Pagination.cjs.map +1 -1
  39. package/dist/Pagination.js +74 -72
  40. package/dist/Pagination.js.map +1 -1
  41. package/dist/Pending.cjs +255 -287
  42. package/dist/Pending.cjs.map +1 -1
  43. package/dist/Pending.js +253 -285
  44. package/dist/Pending.js.map +1 -1
  45. package/dist/PersistentCollection.cjs +242 -285
  46. package/dist/PersistentCollection.cjs.map +1 -1
  47. package/dist/PersistentCollection.js +241 -284
  48. package/dist/PersistentCollection.js.map +1 -1
  49. package/dist/Resource.cjs +166 -174
  50. package/dist/Resource.cjs.map +1 -1
  51. package/dist/Resource.js +164 -172
  52. package/dist/Resource.js.map +1 -1
  53. package/dist/Selection.cjs +84 -94
  54. package/dist/Selection.cjs.map +1 -1
  55. package/dist/Selection.js +83 -93
  56. package/dist/Selection.js.map +1 -1
  57. package/dist/Service.cjs +54 -55
  58. package/dist/Service.cjs.map +1 -1
  59. package/dist/Service.js +53 -54
  60. package/dist/Service.js.map +1 -1
  61. package/dist/Sorting.cjs +102 -101
  62. package/dist/Sorting.cjs.map +1 -1
  63. package/dist/Sorting.js +102 -101
  64. package/dist/Sorting.js.map +1 -1
  65. package/dist/Trackable.cjs +112 -80
  66. package/dist/Trackable.cjs.map +1 -1
  67. package/dist/Trackable.js +111 -79
  68. package/dist/Trackable.js.map +1 -1
  69. package/dist/ViewModel.cjs +528 -576
  70. package/dist/ViewModel.cjs.map +1 -1
  71. package/dist/ViewModel.js +525 -573
  72. package/dist/ViewModel.js.map +1 -1
  73. package/dist/bindPublicMethods.cjs +43 -24
  74. package/dist/bindPublicMethods.cjs.map +1 -1
  75. package/dist/bindPublicMethods.js +43 -24
  76. package/dist/bindPublicMethods.js.map +1 -1
  77. package/dist/errors.cjs +67 -68
  78. package/dist/errors.cjs.map +1 -1
  79. package/dist/errors.js +68 -71
  80. package/dist/errors.js.map +1 -1
  81. package/dist/mvc-kit.cjs +44 -46
  82. package/dist/mvc-kit.js +5 -32
  83. package/dist/produceDraft.cjs +105 -95
  84. package/dist/produceDraft.cjs.map +1 -1
  85. package/dist/produceDraft.js +106 -97
  86. package/dist/produceDraft.js.map +1 -1
  87. package/dist/react/components/CardList.cjs +30 -40
  88. package/dist/react/components/CardList.cjs.map +1 -1
  89. package/dist/react/components/CardList.js +31 -41
  90. package/dist/react/components/CardList.js.map +1 -1
  91. package/dist/react/components/DataTable.cjs +146 -169
  92. package/dist/react/components/DataTable.cjs.map +1 -1
  93. package/dist/react/components/DataTable.js +147 -170
  94. package/dist/react/components/DataTable.js.map +1 -1
  95. package/dist/react/components/InfiniteScroll.cjs +51 -42
  96. package/dist/react/components/InfiniteScroll.cjs.map +1 -1
  97. package/dist/react/components/InfiniteScroll.js +52 -43
  98. package/dist/react/components/InfiniteScroll.js.map +1 -1
  99. package/dist/react/components/types.cjs +10 -6
  100. package/dist/react/components/types.cjs.map +1 -1
  101. package/dist/react/components/types.js +11 -9
  102. package/dist/react/components/types.js.map +1 -1
  103. package/dist/react/guards.cjs +10 -6
  104. package/dist/react/guards.cjs.map +1 -1
  105. package/dist/react/guards.js +11 -9
  106. package/dist/react/guards.js.map +1 -1
  107. package/dist/react/provider.cjs +23 -20
  108. package/dist/react/provider.cjs.map +1 -1
  109. package/dist/react/provider.js +23 -21
  110. package/dist/react/provider.js.map +1 -1
  111. package/dist/react/use-event-bus.cjs +24 -20
  112. package/dist/react/use-event-bus.cjs.map +1 -1
  113. package/dist/react/use-event-bus.js +24 -21
  114. package/dist/react/use-event-bus.js.map +1 -1
  115. package/dist/react/use-instance.cjs +43 -36
  116. package/dist/react/use-instance.cjs.map +1 -1
  117. package/dist/react/use-instance.js +43 -36
  118. package/dist/react/use-instance.js.map +1 -1
  119. package/dist/react/use-local.cjs +48 -64
  120. package/dist/react/use-local.cjs.map +1 -1
  121. package/dist/react/use-local.js +47 -63
  122. package/dist/react/use-local.js.map +1 -1
  123. package/dist/react/use-model.cjs +84 -98
  124. package/dist/react/use-model.cjs.map +1 -1
  125. package/dist/react/use-model.js +84 -100
  126. package/dist/react/use-model.js.map +1 -1
  127. package/dist/react/use-singleton.cjs +19 -23
  128. package/dist/react/use-singleton.cjs.map +1 -1
  129. package/dist/react/use-singleton.js +16 -20
  130. package/dist/react/use-singleton.js.map +1 -1
  131. package/dist/react/use-subscribe-only.cjs +28 -22
  132. package/dist/react/use-subscribe-only.cjs.map +1 -1
  133. package/dist/react/use-subscribe-only.js +28 -22
  134. package/dist/react/use-subscribe-only.js.map +1 -1
  135. package/dist/react/use-teardown.cjs +20 -19
  136. package/dist/react/use-teardown.cjs.map +1 -1
  137. package/dist/react/use-teardown.js +20 -19
  138. package/dist/react/use-teardown.js.map +1 -1
  139. package/dist/react-native/NativeCollection.cjs +98 -78
  140. package/dist/react-native/NativeCollection.cjs.map +1 -1
  141. package/dist/react-native/NativeCollection.js +97 -77
  142. package/dist/react-native/NativeCollection.js.map +1 -1
  143. package/dist/react-native.cjs +2 -4
  144. package/dist/react-native.js +1 -4
  145. package/dist/react.cjs +24 -26
  146. package/dist/react.js +1 -17
  147. package/dist/singleton.cjs +28 -22
  148. package/dist/singleton.cjs.map +1 -1
  149. package/dist/singleton.js +29 -26
  150. package/dist/singleton.js.map +1 -1
  151. package/dist/walkPrototypeChain.cjs +20 -12
  152. package/dist/walkPrototypeChain.cjs.map +1 -1
  153. package/dist/walkPrototypeChain.js +21 -13
  154. package/dist/walkPrototypeChain.js.map +1 -1
  155. package/dist/web/IndexedDBCollection.cjs +53 -36
  156. package/dist/web/IndexedDBCollection.cjs.map +1 -1
  157. package/dist/web/IndexedDBCollection.js +52 -35
  158. package/dist/web/IndexedDBCollection.js.map +1 -1
  159. package/dist/web/WebStorageCollection.cjs +82 -84
  160. package/dist/web/WebStorageCollection.cjs.map +1 -1
  161. package/dist/web/WebStorageCollection.js +81 -83
  162. package/dist/web/WebStorageCollection.js.map +1 -1
  163. package/dist/web/idb.cjs +107 -99
  164. package/dist/web/idb.cjs.map +1 -1
  165. package/dist/web/idb.js +108 -105
  166. package/dist/web/idb.js.map +1 -1
  167. package/dist/web.cjs +4 -6
  168. package/dist/web.js +1 -5
  169. package/dist/wrapAsyncMethods.cjs +141 -168
  170. package/dist/wrapAsyncMethods.cjs.map +1 -1
  171. package/dist/wrapAsyncMethods.js +141 -168
  172. package/dist/wrapAsyncMethods.js.map +1 -1
  173. package/examples/primitive/channel.ts +109 -0
  174. package/examples/primitive/collection.ts +118 -0
  175. package/examples/primitive/controller.ts +118 -0
  176. package/examples/primitive/counter.ts +108 -0
  177. package/examples/primitive/env.d.ts +1 -0
  178. package/examples/primitive/eventbus.ts +77 -0
  179. package/examples/primitive/feed.ts +162 -0
  180. package/examples/primitive/model.ts +82 -0
  181. package/examples/primitive/pagination.ts +91 -0
  182. package/examples/primitive/pending.ts +189 -0
  183. package/examples/primitive/persistent-collection.ts +116 -0
  184. package/examples/primitive/resource.ts +114 -0
  185. package/examples/primitive/selection.ts +96 -0
  186. package/examples/primitive/sorting.ts +112 -0
  187. package/examples/primitive/timer.ts +58 -0
  188. package/examples/primitive/trackable.ts +225 -0
  189. package/examples/primitive/tsconfig.json +20 -0
  190. package/examples/primitive/viewmodel-service.ts +161 -0
  191. package/examples/react/AuthExample/index.html +12 -0
  192. package/examples/react/AuthExample/src/App.tsx +29 -0
  193. package/examples/react/AuthExample/src/components/AdminPage.tsx +51 -0
  194. package/examples/react/AuthExample/src/components/AppHeader.tsx +32 -0
  195. package/examples/react/AuthExample/src/components/AuthGuard.tsx +50 -0
  196. package/examples/react/AuthExample/src/components/AuthScreen.tsx +181 -0
  197. package/examples/react/AuthExample/src/components/DashboardPage.tsx +41 -0
  198. package/examples/react/AuthExample/src/components/ProfilePage.tsx +44 -0
  199. package/examples/react/AuthExample/src/components/Toast.tsx +41 -0
  200. package/examples/react/AuthExample/src/env.d.ts +10 -0
  201. package/examples/react/AuthExample/src/events/AppEventBus.ts +7 -0
  202. package/examples/react/AuthExample/src/main.tsx +10 -0
  203. package/examples/react/AuthExample/src/mock/api.ts +78 -0
  204. package/examples/react/AuthExample/src/models/LoginFormModel.ts +19 -0
  205. package/examples/react/AuthExample/src/models/RegisterFormModel.ts +25 -0
  206. package/examples/react/AuthExample/src/services/AuthService.ts +21 -0
  207. package/examples/react/AuthExample/src/styles.css +445 -0
  208. package/examples/react/AuthExample/src/types/auth.ts +12 -0
  209. package/examples/react/AuthExample/src/viewmodels/AuthViewModel.ts +111 -0
  210. package/examples/react/AuthExample/tsconfig.json +22 -0
  211. package/examples/react/AuthExample/vite.config.ts +18 -0
  212. package/examples/react/ComplexApp/index.html +12 -0
  213. package/examples/react/ComplexApp/src/App.tsx +17 -0
  214. package/examples/react/ComplexApp/src/channels/ActivityChannel.ts +24 -0
  215. package/examples/react/ComplexApp/src/channels/DashboardChannel.ts +26 -0
  216. package/examples/react/ComplexApp/src/channels/ErrorsChannel.ts +5 -0
  217. package/examples/react/ComplexApp/src/channels/LatencyChannel.ts +5 -0
  218. package/examples/react/ComplexApp/src/channels/OrdersChannel.ts +5 -0
  219. package/examples/react/ComplexApp/src/channels/RevenueChannel.ts +5 -0
  220. package/examples/react/ComplexApp/src/channels/TrafficChannel.ts +5 -0
  221. package/examples/react/ComplexApp/src/channels/UsersMetricChannel.ts +5 -0
  222. package/examples/react/ComplexApp/src/collections/DashboardCollection.ts +6 -0
  223. package/examples/react/ComplexApp/src/collections/ErrorsCollection.ts +3 -0
  224. package/examples/react/ComplexApp/src/collections/LatencyCollection.ts +3 -0
  225. package/examples/react/ComplexApp/src/collections/OrdersCollection.ts +3 -0
  226. package/examples/react/ComplexApp/src/collections/RevenueCollection.ts +3 -0
  227. package/examples/react/ComplexApp/src/collections/TrafficCollection.ts +3 -0
  228. package/examples/react/ComplexApp/src/collections/UsersMetricCollection.ts +3 -0
  229. package/examples/react/ComplexApp/src/components/activity/ActivityFeed.tsx +31 -0
  230. package/examples/react/ComplexApp/src/components/activity/ActivityItemRow.tsx +35 -0
  231. package/examples/react/ComplexApp/src/components/dashboard/DashboardCard.tsx +37 -0
  232. package/examples/react/ComplexApp/src/components/dashboard/DashboardPage.tsx +34 -0
  233. package/examples/react/ComplexApp/src/components/layout/Navbar.tsx +32 -0
  234. package/examples/react/ComplexApp/src/components/layout/SocialFeedPanel.tsx +57 -0
  235. package/examples/react/ComplexApp/src/components/shared/Spinner.tsx +3 -0
  236. package/examples/react/ComplexApp/src/components/shared/StatusIndicator.tsx +13 -0
  237. package/examples/react/ComplexApp/src/components/shared/Toast.tsx +40 -0
  238. package/examples/react/ComplexApp/src/env.d.ts +10 -0
  239. package/examples/react/ComplexApp/src/events/AppEventBus.ts +7 -0
  240. package/examples/react/ComplexApp/src/main.tsx +10 -0
  241. package/examples/react/ComplexApp/src/mock-remote/MockWebSocket.ts +38 -0
  242. package/examples/react/ComplexApp/src/mock-remote/activity-api.ts +48 -0
  243. package/examples/react/ComplexApp/src/mock-remote/dashboard-generators.ts +45 -0
  244. package/examples/react/ComplexApp/src/mock-remote/delay.ts +18 -0
  245. package/examples/react/ComplexApp/src/mock-remote/social-api.ts +55 -0
  246. package/examples/react/ComplexApp/src/resources/ActivityResource.ts +12 -0
  247. package/examples/react/ComplexApp/src/resources/SocialFeedResource.ts +17 -0
  248. package/examples/react/ComplexApp/src/styles.css +463 -0
  249. package/examples/react/ComplexApp/src/types/activity.ts +8 -0
  250. package/examples/react/ComplexApp/src/types/dashboard.ts +5 -0
  251. package/examples/react/ComplexApp/src/types/social.ts +8 -0
  252. package/examples/react/ComplexApp/src/types/users.ts +6 -0
  253. package/examples/react/ComplexApp/src/viewmodels/ActivityFeedViewModel.ts +68 -0
  254. package/examples/react/ComplexApp/src/viewmodels/AppStateViewModel.ts +26 -0
  255. package/examples/react/ComplexApp/src/viewmodels/DashboardCardViewModel.ts +69 -0
  256. package/examples/react/ComplexApp/src/viewmodels/ErrorsCardViewModel.ts +9 -0
  257. package/examples/react/ComplexApp/src/viewmodels/LatencyCardViewModel.ts +9 -0
  258. package/examples/react/ComplexApp/src/viewmodels/OrdersCardViewModel.ts +9 -0
  259. package/examples/react/ComplexApp/src/viewmodels/RevenueCardViewModel.ts +9 -0
  260. package/examples/react/ComplexApp/src/viewmodels/SocialFeedViewModel.ts +39 -0
  261. package/examples/react/ComplexApp/src/viewmodels/TrafficCardViewModel.ts +9 -0
  262. package/examples/react/ComplexApp/src/viewmodels/UsersMetricCardViewModel.ts +9 -0
  263. package/examples/react/ComplexApp/tsconfig.json +22 -0
  264. package/examples/react/ComplexApp/vite.config.ts +18 -0
  265. package/examples/react/FullApp/index.html +12 -0
  266. package/examples/react/FullApp/src/App.tsx +28 -0
  267. package/examples/react/FullApp/src/collections/ConversationsCollection.ts +4 -0
  268. package/examples/react/FullApp/src/collections/LocationsCollection.ts +4 -0
  269. package/examples/react/FullApp/src/components/auth/LoginPage.tsx +80 -0
  270. package/examples/react/FullApp/src/components/dashboard/DashboardPage.tsx +29 -0
  271. package/examples/react/FullApp/src/components/dashboard/RecentActivityCard.tsx +35 -0
  272. package/examples/react/FullApp/src/components/dashboard/StatsCard.tsx +19 -0
  273. package/examples/react/FullApp/src/components/layout/AppShell.tsx +31 -0
  274. package/examples/react/FullApp/src/components/layout/Header.tsx +25 -0
  275. package/examples/react/FullApp/src/components/layout/Sidebar.tsx +29 -0
  276. package/examples/react/FullApp/src/components/locations/LocationFilters.tsx +60 -0
  277. package/examples/react/FullApp/src/components/locations/LocationForm.tsx +112 -0
  278. package/examples/react/FullApp/src/components/locations/LocationProfilePage.tsx +81 -0
  279. package/examples/react/FullApp/src/components/locations/LocationsPage.tsx +127 -0
  280. package/examples/react/FullApp/src/components/messaging/ConversationList.tsx +59 -0
  281. package/examples/react/FullApp/src/components/messaging/MessageBubble.tsx +22 -0
  282. package/examples/react/FullApp/src/components/messaging/MessageThread.tsx +100 -0
  283. package/examples/react/FullApp/src/components/messaging/MessagingPage.tsx +52 -0
  284. package/examples/react/FullApp/src/components/shared/ErrorBanner.tsx +3 -0
  285. package/examples/react/FullApp/src/components/shared/Spinner.tsx +7 -0
  286. package/examples/react/FullApp/src/components/shared/Toast.tsx +41 -0
  287. package/examples/react/FullApp/src/components/users/UserFilters.tsx +59 -0
  288. package/examples/react/FullApp/src/components/users/UsersPage.tsx +80 -0
  289. package/examples/react/FullApp/src/components/users/UsersTable.tsx +52 -0
  290. package/examples/react/FullApp/src/env.d.ts +10 -0
  291. package/examples/react/FullApp/src/events/AppEventBus.ts +7 -0
  292. package/examples/react/FullApp/src/main.tsx +10 -0
  293. package/examples/react/FullApp/src/mock/delay.ts +21 -0
  294. package/examples/react/FullApp/src/mock/locations.ts +76 -0
  295. package/examples/react/FullApp/src/mock/messages.ts +237 -0
  296. package/examples/react/FullApp/src/mock/users.ts +84 -0
  297. package/examples/react/FullApp/src/models/LocationFormModel.ts +31 -0
  298. package/examples/react/FullApp/src/models/LoginFormModel.ts +19 -0
  299. package/examples/react/FullApp/src/resources/UsersResource.ts +12 -0
  300. package/examples/react/FullApp/src/services/AuthService.ts +18 -0
  301. package/examples/react/FullApp/src/services/LocationService.ts +23 -0
  302. package/examples/react/FullApp/src/services/MessageService.ts +65 -0
  303. package/examples/react/FullApp/src/services/UserService.ts +23 -0
  304. package/examples/react/FullApp/src/styles.css +767 -0
  305. package/examples/react/FullApp/src/types/conversation.ts +7 -0
  306. package/examples/react/FullApp/src/types/location.ts +12 -0
  307. package/examples/react/FullApp/src/types/message.ts +7 -0
  308. package/examples/react/FullApp/src/types/user.ts +10 -0
  309. package/examples/react/FullApp/src/viewmodels/AuthViewModel.ts +51 -0
  310. package/examples/react/FullApp/src/viewmodels/ConversationsViewModel.ts +89 -0
  311. package/examples/react/FullApp/src/viewmodels/DashboardViewModel.ts +56 -0
  312. package/examples/react/FullApp/src/viewmodels/LocationProfileViewModel.ts +81 -0
  313. package/examples/react/FullApp/src/viewmodels/LocationsViewModel.ts +113 -0
  314. package/examples/react/FullApp/src/viewmodels/MessageThreadViewModel.ts +83 -0
  315. package/examples/react/FullApp/src/viewmodels/UsersViewModel.ts +88 -0
  316. package/examples/react/FullApp/tsconfig.json +22 -0
  317. package/examples/react/FullApp/vite.config.ts +18 -0
  318. package/examples/react/WorkerApp/index.html +12 -0
  319. package/examples/react/WorkerApp/src/App.tsx +24 -0
  320. package/examples/react/WorkerApp/src/channels/MessagingChannel.ts +46 -0
  321. package/examples/react/WorkerApp/src/channels/WorkerStatusChannel.ts +35 -0
  322. package/examples/react/WorkerApp/src/components/auth/LoginPage.tsx +60 -0
  323. package/examples/react/WorkerApp/src/components/layout/AppShell.tsx +31 -0
  324. package/examples/react/WorkerApp/src/components/layout/Header.tsx +23 -0
  325. package/examples/react/WorkerApp/src/components/layout/Sidebar.tsx +28 -0
  326. package/examples/react/WorkerApp/src/components/messaging/ComposeBar.tsx +33 -0
  327. package/examples/react/WorkerApp/src/components/messaging/ConversationList.tsx +59 -0
  328. package/examples/react/WorkerApp/src/components/messaging/MessageBubble.tsx +45 -0
  329. package/examples/react/WorkerApp/src/components/messaging/MessageThread.tsx +93 -0
  330. package/examples/react/WorkerApp/src/components/messaging/MessagingPage.tsx +53 -0
  331. package/examples/react/WorkerApp/src/components/shared/ErrorBanner.tsx +3 -0
  332. package/examples/react/WorkerApp/src/components/shared/PendingBanner.tsx +37 -0
  333. package/examples/react/WorkerApp/src/components/shared/Spinner.tsx +7 -0
  334. package/examples/react/WorkerApp/src/components/shared/Toast.tsx +41 -0
  335. package/examples/react/WorkerApp/src/components/shift/ShiftPage.tsx +98 -0
  336. package/examples/react/WorkerApp/src/components/shift/ShiftTimer.tsx +24 -0
  337. package/examples/react/WorkerApp/src/components/shift/SiteSelector.tsx +27 -0
  338. package/examples/react/WorkerApp/src/components/sites/SiteFilters.tsx +61 -0
  339. package/examples/react/WorkerApp/src/components/sites/SitesPage.tsx +102 -0
  340. package/examples/react/WorkerApp/src/env.d.ts +10 -0
  341. package/examples/react/WorkerApp/src/events/AppEventBus.ts +7 -0
  342. package/examples/react/WorkerApp/src/main.tsx +10 -0
  343. package/examples/react/WorkerApp/src/mock/MockWebSocket.ts +38 -0
  344. package/examples/react/WorkerApp/src/mock/delay.ts +31 -0
  345. package/examples/react/WorkerApp/src/mock/messages.ts +120 -0
  346. package/examples/react/WorkerApp/src/mock/shifts.ts +57 -0
  347. package/examples/react/WorkerApp/src/mock/sites.ts +14 -0
  348. package/examples/react/WorkerApp/src/mock/workers.ts +12 -0
  349. package/examples/react/WorkerApp/src/models/ComposeMessageModel.ts +17 -0
  350. package/examples/react/WorkerApp/src/resources/ConversationsResource.ts +10 -0
  351. package/examples/react/WorkerApp/src/resources/MessagesResource.ts +32 -0
  352. package/examples/react/WorkerApp/src/resources/ShiftResource.ts +73 -0
  353. package/examples/react/WorkerApp/src/resources/SitesResource.ts +11 -0
  354. package/examples/react/WorkerApp/src/resources/WorkersResource.ts +11 -0
  355. package/examples/react/WorkerApp/src/styles.css +756 -0
  356. package/examples/react/WorkerApp/src/types/conversation.ts +7 -0
  357. package/examples/react/WorkerApp/src/types/message.ts +7 -0
  358. package/examples/react/WorkerApp/src/types/shift.ts +13 -0
  359. package/examples/react/WorkerApp/src/types/site.ts +8 -0
  360. package/examples/react/WorkerApp/src/types/worker.ts +8 -0
  361. package/examples/react/WorkerApp/src/viewmodels/AuthViewModel.ts +41 -0
  362. package/examples/react/WorkerApp/src/viewmodels/ConversationsViewModel.ts +83 -0
  363. package/examples/react/WorkerApp/src/viewmodels/MessageThreadViewModel.ts +113 -0
  364. package/examples/react/WorkerApp/src/viewmodels/ShiftViewModel.ts +147 -0
  365. package/examples/react/WorkerApp/src/viewmodels/SitesViewModel.ts +82 -0
  366. package/examples/react/WorkerApp/tsconfig.json +22 -0
  367. package/examples/react/WorkerApp/vite.config.ts +18 -0
  368. package/package.json +11 -9
  369. package/src/Pending.test.ts +1 -2
  370. package/src/Sorting.test.ts +1 -1
  371. package/src/produceDraft.test.ts +3 -3
  372. package/src/react/components/CardList.test.tsx +1 -1
  373. package/src/react/components/DataTable.test.tsx +1 -1
  374. package/src/react/components/InfiniteScroll.test.tsx +5 -5
  375. package/dist/mvc-kit.cjs.map +0 -1
  376. package/dist/mvc-kit.js.map +0 -1
  377. package/dist/react-native.cjs.map +0 -1
  378. package/dist/react-native.js.map +0 -1
  379. package/dist/react.cjs.map +0 -1
  380. package/dist/react.js.map +0 -1
  381. package/dist/web.cjs.map +0 -1
  382. package/dist/web.js.map +0 -1
@@ -0,0 +1,76 @@
1
+ import type { LocationState } from '../types/location';
2
+
3
+ export const mockLocations: LocationState[] = [
4
+ {
5
+ id: 'loc1',
6
+ name: 'Downtown HQ',
7
+ type: 'office',
8
+ status: 'active',
9
+ city: 'New York',
10
+ state: 'NY',
11
+ address: '100 Broadway',
12
+ capacity: 250,
13
+ managerId: 'u1',
14
+ createdAt: '2024-01-01T00:00:00Z',
15
+ },
16
+ {
17
+ id: 'loc2',
18
+ name: 'West Warehouse',
19
+ type: 'warehouse',
20
+ status: 'active',
21
+ city: 'Chicago',
22
+ state: 'IL',
23
+ address: '500 Industrial Ave',
24
+ capacity: 800,
25
+ managerId: 'u2',
26
+ createdAt: '2024-02-01T00:00:00Z',
27
+ },
28
+ {
29
+ id: 'loc3',
30
+ name: 'Main Street Store',
31
+ type: 'retail',
32
+ status: 'active',
33
+ city: 'Los Angeles',
34
+ state: 'CA',
35
+ address: '220 Main St',
36
+ capacity: 50,
37
+ managerId: 'u5',
38
+ createdAt: '2024-03-01T00:00:00Z',
39
+ },
40
+ {
41
+ id: 'loc4',
42
+ name: 'North Office',
43
+ type: 'office',
44
+ status: 'maintenance',
45
+ city: 'Seattle',
46
+ state: 'WA',
47
+ address: '75 Pine St',
48
+ capacity: 120,
49
+ managerId: 'u7',
50
+ createdAt: '2024-04-01T00:00:00Z',
51
+ },
52
+ {
53
+ id: 'loc5',
54
+ name: 'East Distribution Center',
55
+ type: 'warehouse',
56
+ status: 'active',
57
+ city: 'Miami',
58
+ state: 'FL',
59
+ address: '1200 Port Blvd',
60
+ capacity: 1500,
61
+ managerId: 'u2',
62
+ createdAt: '2024-05-01T00:00:00Z',
63
+ },
64
+ {
65
+ id: 'loc6',
66
+ name: 'Harbor Retail',
67
+ type: 'retail',
68
+ status: 'inactive',
69
+ city: 'San Francisco',
70
+ state: 'CA',
71
+ address: '45 Pier Ave',
72
+ capacity: 40,
73
+ managerId: 'u5',
74
+ createdAt: '2024-06-01T00:00:00Z',
75
+ },
76
+ ];
@@ -0,0 +1,237 @@
1
+ import type { ConversationState } from '../types/conversation';
2
+ import type { MessageState } from '../types/message';
3
+
4
+ export const mockConversations: ConversationState[] = [
5
+ {
6
+ id: 'conv1',
7
+ participantIds: ['u1', 'u2'],
8
+ lastMessage: 'Sure, I will review the report today.',
9
+ lastMessageAt: '2024-07-10T14:30:00Z',
10
+ unreadCount: 2,
11
+ },
12
+ {
13
+ id: 'conv2',
14
+ participantIds: ['u1', 'u5'],
15
+ lastMessage: 'The new store layout looks great!',
16
+ lastMessageAt: '2024-07-10T11:15:00Z',
17
+ unreadCount: 0,
18
+ },
19
+ {
20
+ id: 'conv3',
21
+ participantIds: ['u2', 'u5', 'u6'],
22
+ lastMessage: 'Meeting moved to 3pm tomorrow.',
23
+ lastMessageAt: '2024-07-09T17:45:00Z',
24
+ unreadCount: 1,
25
+ },
26
+ ];
27
+
28
+ export const mockMessages: MessageState[] = [
29
+ // conv1: Alice & Bob — extended conversation (~20 messages for Feed pagination)
30
+ {
31
+ id: 'msg-c1-01',
32
+ conversationId: 'conv1',
33
+ senderId: 'u1',
34
+ text: 'Good morning Bob! Quick question about the warehouse.',
35
+ sentAt: '2024-07-10T09:00:00Z',
36
+ },
37
+ {
38
+ id: 'msg-c1-02',
39
+ conversationId: 'conv1',
40
+ senderId: 'u2',
41
+ text: 'Morning Alice! What do you need?',
42
+ sentAt: '2024-07-10T09:05:00Z',
43
+ },
44
+ {
45
+ id: 'msg-c1-03',
46
+ conversationId: 'conv1',
47
+ senderId: 'u1',
48
+ text: 'Do we have the updated capacity numbers for the Chicago warehouse?',
49
+ sentAt: '2024-07-10T09:10:00Z',
50
+ },
51
+ {
52
+ id: 'msg-c1-04',
53
+ conversationId: 'conv1',
54
+ senderId: 'u2',
55
+ text: 'Let me check. I think the facilities team sent those over last week.',
56
+ sentAt: '2024-07-10T09:15:00Z',
57
+ },
58
+ {
59
+ id: 'msg-c1-05',
60
+ conversationId: 'conv1',
61
+ senderId: 'u2',
62
+ text: 'Found it — Chicago is at 85% capacity right now. They expanded the east wing.',
63
+ sentAt: '2024-07-10T09:25:00Z',
64
+ },
65
+ {
66
+ id: 'msg-c1-06',
67
+ conversationId: 'conv1',
68
+ senderId: 'u1',
69
+ text: 'That is higher than I expected. Are we planning any overflow to the Detroit facility?',
70
+ sentAt: '2024-07-10T09:30:00Z',
71
+ },
72
+ {
73
+ id: 'msg-c1-07',
74
+ conversationId: 'conv1',
75
+ senderId: 'u2',
76
+ text: 'Yeah, Frank mentioned routing some of the Q3 shipments there. I will get the details.',
77
+ sentAt: '2024-07-10T09:40:00Z',
78
+ },
79
+ {
80
+ id: 'msg-c1-08',
81
+ conversationId: 'conv1',
82
+ senderId: 'u1',
83
+ text: 'Perfect. Can you also pull the inventory report while you are at it?',
84
+ sentAt: '2024-07-10T09:45:00Z',
85
+ },
86
+ {
87
+ id: 'msg-c1-09',
88
+ conversationId: 'conv1',
89
+ senderId: 'u2',
90
+ text: 'Sure thing. I will have both ready by lunch.',
91
+ sentAt: '2024-07-10T09:50:00Z',
92
+ },
93
+ {
94
+ id: 'msg-c1-10',
95
+ conversationId: 'conv1',
96
+ senderId: 'u1',
97
+ text: 'Thanks Bob. Also, did the new barcode scanners arrive?',
98
+ sentAt: '2024-07-10T10:15:00Z',
99
+ },
100
+ {
101
+ id: 'msg-c1-11',
102
+ conversationId: 'conv1',
103
+ senderId: 'u2',
104
+ text: 'They came in yesterday. The team is setting them up now.',
105
+ sentAt: '2024-07-10T10:20:00Z',
106
+ },
107
+ {
108
+ id: 'msg-c1-12',
109
+ conversationId: 'conv1',
110
+ senderId: 'u1',
111
+ text: 'Great. Make sure the firmware is updated before they go live.',
112
+ sentAt: '2024-07-10T10:30:00Z',
113
+ },
114
+ {
115
+ id: 'msg-c1-13',
116
+ conversationId: 'conv1',
117
+ senderId: 'u2',
118
+ text: 'Already on it. IT is handling the firmware updates this afternoon.',
119
+ sentAt: '2024-07-10T10:45:00Z',
120
+ },
121
+ {
122
+ id: 'msg-c1-14',
123
+ conversationId: 'conv1',
124
+ senderId: 'u1',
125
+ text: 'One more thing — the safety inspection for the loading dock is next Tuesday.',
126
+ sentAt: '2024-07-10T11:00:00Z',
127
+ },
128
+ {
129
+ id: 'msg-c1-15',
130
+ conversationId: 'conv1',
131
+ senderId: 'u2',
132
+ text: 'Got it on the calendar. I will make sure the dock area is prepped.',
133
+ sentAt: '2024-07-10T11:15:00Z',
134
+ },
135
+ {
136
+ id: 'msg-c1-16',
137
+ conversationId: 'conv1',
138
+ senderId: 'u1',
139
+ text: 'How is the Chicago shipment going by the way?',
140
+ sentAt: '2024-07-10T13:00:00Z',
141
+ },
142
+ {
143
+ id: 'msg-c1-17',
144
+ conversationId: 'conv1',
145
+ senderId: 'u2',
146
+ text: 'Running a bit behind. Truck was delayed but should arrive by 3pm.',
147
+ sentAt: '2024-07-10T13:15:00Z',
148
+ },
149
+ {
150
+ id: 'msg-c1-18',
151
+ conversationId: 'conv1',
152
+ senderId: 'u1',
153
+ text: 'OK keep me posted. Hey did you get a chance to look at the warehouse inventory report?',
154
+ sentAt: '2024-07-10T14:00:00Z',
155
+ },
156
+ {
157
+ id: 'msg-c1-19',
158
+ conversationId: 'conv1',
159
+ senderId: 'u2',
160
+ text: 'Not yet, been busy with the Chicago shipment all morning.',
161
+ sentAt: '2024-07-10T14:10:00Z',
162
+ },
163
+ {
164
+ id: 'msg-c1-20',
165
+ conversationId: 'conv1',
166
+ senderId: 'u1',
167
+ text: 'No worries, just need it by end of week. Let me know if you have questions.',
168
+ sentAt: '2024-07-10T14:20:00Z',
169
+ },
170
+ {
171
+ id: 'msg-c1-21',
172
+ conversationId: 'conv1',
173
+ senderId: 'u2',
174
+ text: 'Sure, I will review the report today.',
175
+ sentAt: '2024-07-10T14:30:00Z',
176
+ },
177
+
178
+ // conv2: Alice & Eva
179
+ {
180
+ id: 'msg5',
181
+ conversationId: 'conv2',
182
+ senderId: 'u5',
183
+ text: 'Alice, I finished the floor plan for the new Main Street layout.',
184
+ sentAt: '2024-07-10T10:30:00Z',
185
+ },
186
+ {
187
+ id: 'msg6',
188
+ conversationId: 'conv2',
189
+ senderId: 'u1',
190
+ text: 'That was fast! Can you share the mockups?',
191
+ sentAt: '2024-07-10T10:45:00Z',
192
+ },
193
+ {
194
+ id: 'msg7',
195
+ conversationId: 'conv2',
196
+ senderId: 'u5',
197
+ text: 'Just uploaded them to the shared drive. Check the Locations folder.',
198
+ sentAt: '2024-07-10T11:00:00Z',
199
+ },
200
+ {
201
+ id: 'msg8',
202
+ conversationId: 'conv2',
203
+ senderId: 'u1',
204
+ text: 'The new store layout looks great!',
205
+ sentAt: '2024-07-10T11:15:00Z',
206
+ },
207
+
208
+ // conv3: Bob, Eva & Frank
209
+ {
210
+ id: 'msg9',
211
+ conversationId: 'conv3',
212
+ senderId: 'u2',
213
+ text: 'Team, we need to discuss the Q3 distribution plan.',
214
+ sentAt: '2024-07-09T16:00:00Z',
215
+ },
216
+ {
217
+ id: 'msg10',
218
+ conversationId: 'conv3',
219
+ senderId: 'u6',
220
+ text: 'I can put together the shipping estimates by tomorrow.',
221
+ sentAt: '2024-07-09T16:30:00Z',
222
+ },
223
+ {
224
+ id: 'msg11',
225
+ conversationId: 'conv3',
226
+ senderId: 'u5',
227
+ text: 'Great. Let us sync tomorrow afternoon then.',
228
+ sentAt: '2024-07-09T17:00:00Z',
229
+ },
230
+ {
231
+ id: 'msg12',
232
+ conversationId: 'conv3',
233
+ senderId: 'u2',
234
+ text: 'Meeting moved to 3pm tomorrow.',
235
+ sentAt: '2024-07-09T17:45:00Z',
236
+ },
237
+ ];
@@ -0,0 +1,84 @@
1
+ import type { UserState } from '../types/user';
2
+
3
+ export const mockUsers: UserState[] = [
4
+ {
5
+ id: 'u1',
6
+ firstName: 'Alice',
7
+ lastName: 'Johnson',
8
+ email: 'alice@example.com',
9
+ role: 'admin',
10
+ status: 'active',
11
+ avatarUrl: '',
12
+ createdAt: '2024-01-15T10:00:00Z',
13
+ },
14
+ {
15
+ id: 'u2',
16
+ firstName: 'Bob',
17
+ lastName: 'Smith',
18
+ email: 'bob@example.com',
19
+ role: 'manager',
20
+ status: 'active',
21
+ avatarUrl: '',
22
+ createdAt: '2024-02-20T14:30:00Z',
23
+ },
24
+ {
25
+ id: 'u3',
26
+ firstName: 'Carol',
27
+ lastName: 'Davis',
28
+ email: 'carol@example.com',
29
+ role: 'member',
30
+ status: 'active',
31
+ avatarUrl: '',
32
+ createdAt: '2024-03-10T09:15:00Z',
33
+ },
34
+ {
35
+ id: 'u4',
36
+ firstName: 'Dan',
37
+ lastName: 'Wilson',
38
+ email: 'dan@example.com',
39
+ role: 'member',
40
+ status: 'inactive',
41
+ avatarUrl: '',
42
+ createdAt: '2024-03-25T16:45:00Z',
43
+ },
44
+ {
45
+ id: 'u5',
46
+ firstName: 'Eva',
47
+ lastName: 'Martinez',
48
+ email: 'eva@example.com',
49
+ role: 'manager',
50
+ status: 'active',
51
+ avatarUrl: '',
52
+ createdAt: '2024-04-05T11:00:00Z',
53
+ },
54
+ {
55
+ id: 'u6',
56
+ firstName: 'Frank',
57
+ lastName: 'Brown',
58
+ email: 'frank@example.com',
59
+ role: 'member',
60
+ status: 'active',
61
+ avatarUrl: '',
62
+ createdAt: '2024-05-12T08:30:00Z',
63
+ },
64
+ {
65
+ id: 'u7',
66
+ firstName: 'Grace',
67
+ lastName: 'Lee',
68
+ email: 'grace@example.com',
69
+ role: 'admin',
70
+ status: 'active',
71
+ avatarUrl: '',
72
+ createdAt: '2024-06-01T13:20:00Z',
73
+ },
74
+ {
75
+ id: 'u8',
76
+ firstName: 'Henry',
77
+ lastName: 'Taylor',
78
+ email: 'henry@example.com',
79
+ role: 'member',
80
+ status: 'inactive',
81
+ avatarUrl: '',
82
+ createdAt: '2024-06-18T15:10:00Z',
83
+ },
84
+ ];
@@ -0,0 +1,31 @@
1
+ import { Model } from 'mvc-kit';
2
+ import type { ValidationErrors } from 'mvc-kit';
3
+ import type { LocationState } from '../types/location';
4
+
5
+ export interface LocationFormState {
6
+ name: string;
7
+ type: LocationState['type'];
8
+ city: string;
9
+ state: string;
10
+ address: string;
11
+ capacity: number;
12
+ }
13
+
14
+ export class LocationFormModel extends Model<LocationFormState> {
15
+ setName(name: string) { this.set({ name }); }
16
+ setType(type: LocationFormState['type']) { this.set({ type }); }
17
+ setCity(city: string) { this.set({ city }); }
18
+ setStateName(state: string) { this.set({ state }); }
19
+ setAddress(address: string) { this.set({ address }); }
20
+ setCapacity(capacity: number) { this.set({ capacity }); }
21
+
22
+ protected validate(state: LocationFormState): ValidationErrors<LocationFormState> {
23
+ const errors: Partial<Record<keyof LocationFormState, string>> = {};
24
+ if (!state.name.trim()) errors.name = 'Name is required';
25
+ if (!state.city.trim()) errors.city = 'City is required';
26
+ if (!state.state.trim()) errors.state = 'State is required';
27
+ if (!state.address.trim()) errors.address = 'Address is required';
28
+ if (state.capacity <= 0) errors.capacity = 'Must be greater than 0';
29
+ return errors;
30
+ }
31
+ }
@@ -0,0 +1,19 @@
1
+ import { Model } from 'mvc-kit';
2
+ import type { ValidationErrors } from 'mvc-kit';
3
+
4
+ export interface LoginFormState {
5
+ email: string;
6
+ password: string;
7
+ }
8
+
9
+ export class LoginFormModel extends Model<LoginFormState> {
10
+ setEmail(email: string) { this.set({ email }); }
11
+ setPassword(password: string) { this.set({ password }); }
12
+
13
+ protected validate(state: LoginFormState): ValidationErrors<LoginFormState> {
14
+ const errors: Partial<Record<keyof LoginFormState, string>> = {};
15
+ if (!state.email.includes('@')) errors.email = 'Valid email required';
16
+ if (state.password.length < 6) errors.password = 'Must be at least 6 characters';
17
+ return errors;
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ import { Resource, singleton } from 'mvc-kit';
2
+ import type { UserState } from '../types/user';
3
+ import { UserService } from '../services/UserService';
4
+
5
+ export class UsersResource extends Resource<UserState> {
6
+ private api = singleton(UserService);
7
+
8
+ async loadAll() {
9
+ const data = await this.api.getAll(this.disposeSignal);
10
+ this.reset(data);
11
+ }
12
+ }
@@ -0,0 +1,18 @@
1
+ import { Service, HttpError } from 'mvc-kit';
2
+ import type { UserState } from '../types/user';
3
+ import { mockUsers } from '../mock/users';
4
+ import { mockFetch } from '../mock/delay';
5
+
6
+ export class AuthService extends Service {
7
+ async login(email: string, password: string, signal?: AbortSignal): Promise<UserState> {
8
+ if (!email.includes('@') || password.length < 6) {
9
+ throw new HttpError(401, 'Invalid credentials');
10
+ }
11
+ // Return Alice as the logged-in user
12
+ return mockFetch({ ...mockUsers[0] }, 400, signal);
13
+ }
14
+
15
+ async logout(signal?: AbortSignal): Promise<void> {
16
+ await mockFetch(undefined, 200, signal);
17
+ }
18
+ }
@@ -0,0 +1,23 @@
1
+ import { Service, HttpError } from 'mvc-kit';
2
+ import type { LocationState } from '../types/location';
3
+ import { mockLocations } from '../mock/locations';
4
+ import { mockFetch } from '../mock/delay';
5
+
6
+ export class LocationService extends Service {
7
+ async getAll(signal?: AbortSignal): Promise<LocationState[]> {
8
+ return mockFetch(mockLocations.map(l => ({ ...l })), 300, signal);
9
+ }
10
+
11
+ async getById(id: string, signal?: AbortSignal): Promise<LocationState> {
12
+ const location = mockLocations.find(l => l.id === id);
13
+ if (!location) throw new HttpError(404, 'Location not found');
14
+ return mockFetch({ ...location }, 200, signal);
15
+ }
16
+
17
+ async update(id: string, data: Partial<LocationState>, signal?: AbortSignal): Promise<LocationState> {
18
+ const idx = mockLocations.findIndex(l => l.id === id);
19
+ if (idx === -1) throw new HttpError(404, 'Location not found');
20
+ Object.assign(mockLocations[idx], data);
21
+ return mockFetch({ ...mockLocations[idx] }, 400, signal);
22
+ }
23
+ }
@@ -0,0 +1,65 @@
1
+ import { Service } from 'mvc-kit';
2
+ import type { FeedPage } from 'mvc-kit';
3
+ import type { ConversationState } from '../types/conversation';
4
+ import type { MessageState } from '../types/message';
5
+ import { mockConversations, mockMessages } from '../mock/messages';
6
+ import { mockFetch } from '../mock/delay';
7
+
8
+ export class MessageService extends Service {
9
+ async getConversations(userId: string, signal?: AbortSignal): Promise<ConversationState[]> {
10
+ const conversations = mockConversations
11
+ .filter(c => c.participantIds.includes(userId))
12
+ .map(c => ({ ...c }));
13
+ return mockFetch(conversations, 300, signal);
14
+ }
15
+
16
+ async getMessages(
17
+ conversationId: string,
18
+ signal?: AbortSignal,
19
+ options?: { cursor?: string | null; limit?: number },
20
+ ): Promise<FeedPage<MessageState>> {
21
+ const limit = options?.limit ?? 8;
22
+
23
+ // All messages for this conversation, sorted newest-first
24
+ let messages = mockMessages
25
+ .filter(m => m.conversationId === conversationId)
26
+ .sort((a, b) => new Date(b.sentAt).getTime() - new Date(a.sentAt).getTime());
27
+
28
+ // If cursor provided, filter to messages older than cursor
29
+ if (options?.cursor) {
30
+ const cursorTime = new Date(options.cursor).getTime();
31
+ messages = messages.filter(m => new Date(m.sentAt).getTime() < cursorTime);
32
+ }
33
+
34
+ const page = messages.slice(0, limit).map(m => ({ ...m }));
35
+ const hasMore = messages.length > limit;
36
+ const cursor = page.length > 0 ? page[page.length - 1].sentAt : null;
37
+
38
+ return mockFetch({ items: page, hasMore, cursor }, 250, signal);
39
+ }
40
+
41
+ async sendMessage(
42
+ conversationId: string,
43
+ senderId: string,
44
+ text: string,
45
+ signal?: AbortSignal,
46
+ ): Promise<MessageState> {
47
+ const message: MessageState = {
48
+ id: `msg-${Date.now()}`,
49
+ conversationId,
50
+ senderId,
51
+ text,
52
+ sentAt: new Date().toISOString(),
53
+ };
54
+ mockMessages.push(message);
55
+
56
+ // Update conversation's last message
57
+ const conv = mockConversations.find(c => c.id === conversationId);
58
+ if (conv) {
59
+ conv.lastMessage = text;
60
+ conv.lastMessageAt = message.sentAt;
61
+ }
62
+
63
+ return mockFetch(message, 200, signal);
64
+ }
65
+ }
@@ -0,0 +1,23 @@
1
+ import { Service, HttpError } from 'mvc-kit';
2
+ import type { UserState } from '../types/user';
3
+ import { mockUsers } from '../mock/users';
4
+ import { mockFetch } from '../mock/delay';
5
+
6
+ export class UserService extends Service {
7
+ async getAll(signal?: AbortSignal): Promise<UserState[]> {
8
+ return mockFetch(mockUsers.map(u => ({ ...u })), 300, signal);
9
+ }
10
+
11
+ async getById(id: string, signal?: AbortSignal): Promise<UserState> {
12
+ const user = mockUsers.find(u => u.id === id);
13
+ if (!user) throw new HttpError(404, 'User not found');
14
+ return mockFetch({ ...user }, 200, signal);
15
+ }
16
+
17
+ async update(id: string, data: Partial<UserState>, signal?: AbortSignal): Promise<UserState> {
18
+ const idx = mockUsers.findIndex(u => u.id === id);
19
+ if (idx === -1) throw new HttpError(404, 'User not found');
20
+ Object.assign(mockUsers[idx], data);
21
+ return mockFetch({ ...mockUsers[idx] }, 300, signal);
22
+ }
23
+ }