snice 2.5.4 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/README.md +501 -882
  2. package/bin/templates/base/src/components/counter-button.ts +13 -26
  3. package/bin/templates/base/src/controllers/counter-controller.ts +3 -3
  4. package/dist/components/accordion/snice-accordion-item.d.ts +4 -5
  5. package/dist/components/accordion/snice-accordion-item.js +37 -39
  6. package/dist/components/accordion/snice-accordion-item.js.map +1 -1
  7. package/dist/components/accordion/snice-accordion.d.ts +5 -11
  8. package/dist/components/accordion/snice-accordion.js +51 -52
  9. package/dist/components/accordion/snice-accordion.js.map +1 -1
  10. package/dist/components/alert/snice-alert.d.ts +2 -6
  11. package/dist/components/alert/snice-alert.js +41 -56
  12. package/dist/components/alert/snice-alert.js.map +1 -1
  13. package/dist/components/avatar/snice-avatar.d.ts +2 -6
  14. package/dist/components/avatar/snice-avatar.js +64 -71
  15. package/dist/components/avatar/snice-avatar.js.map +1 -1
  16. package/dist/components/badge/snice-badge.d.ts +2 -3
  17. package/dist/components/badge/snice-badge.js +22 -23
  18. package/dist/components/badge/snice-badge.js.map +1 -1
  19. package/dist/components/breadcrumbs/snice-breadcrumbs.d.ts +5 -12
  20. package/dist/components/breadcrumbs/snice-breadcrumbs.js +88 -89
  21. package/dist/components/breadcrumbs/snice-breadcrumbs.js.map +1 -1
  22. package/dist/components/button/snice-button.d.ts +3 -7
  23. package/dist/components/button/snice-button.js +37 -58
  24. package/dist/components/button/snice-button.js.map +1 -1
  25. package/dist/components/card/snice-card.d.ts +5 -8
  26. package/dist/components/card/snice-card.js +71 -56
  27. package/dist/components/card/snice-card.js.map +1 -1
  28. package/dist/components/checkbox/snice-checkbox.d.ts +4 -13
  29. package/dist/components/checkbox/snice-checkbox.js +66 -137
  30. package/dist/components/checkbox/snice-checkbox.js.map +1 -1
  31. package/dist/components/chip/snice-chip.d.ts +5 -11
  32. package/dist/components/chip/snice-chip.js +44 -47
  33. package/dist/components/chip/snice-chip.js.map +1 -1
  34. package/dist/components/date-picker/snice-date-picker.d.ts +11 -11
  35. package/dist/components/date-picker/snice-date-picker.js +134 -133
  36. package/dist/components/date-picker/snice-date-picker.js.map +1 -1
  37. package/dist/components/divider/snice-divider.d.ts +2 -4
  38. package/dist/components/divider/snice-divider.js +14 -22
  39. package/dist/components/divider/snice-divider.js.map +1 -1
  40. package/dist/components/drawer/snice-drawer.d.ts +4 -4
  41. package/dist/components/drawer/snice-drawer.js +25 -19
  42. package/dist/components/drawer/snice-drawer.js.map +1 -1
  43. package/dist/components/input/snice-input.d.ts +8 -6
  44. package/dist/components/input/snice-input.js +122 -105
  45. package/dist/components/input/snice-input.js.map +1 -1
  46. package/dist/components/layout/snice-layout-blog.d.ts +4 -4
  47. package/dist/components/layout/snice-layout-blog.js +21 -19
  48. package/dist/components/layout/snice-layout-blog.js.map +1 -1
  49. package/dist/components/layout/snice-layout-card.d.ts +2 -2
  50. package/dist/components/layout/snice-layout-card.js +16 -9
  51. package/dist/components/layout/snice-layout-card.js.map +1 -1
  52. package/dist/components/layout/snice-layout-centered.d.ts +2 -2
  53. package/dist/components/layout/snice-layout-centered.js +14 -7
  54. package/dist/components/layout/snice-layout-centered.js.map +1 -1
  55. package/dist/components/layout/snice-layout-dashboard.d.ts +5 -5
  56. package/dist/components/layout/snice-layout-dashboard.js +38 -30
  57. package/dist/components/layout/snice-layout-dashboard.js.map +1 -1
  58. package/dist/components/layout/snice-layout-fullscreen.d.ts +2 -2
  59. package/dist/components/layout/snice-layout-fullscreen.js +17 -10
  60. package/dist/components/layout/snice-layout-fullscreen.js.map +1 -1
  61. package/dist/components/layout/snice-layout-landing.d.ts +4 -4
  62. package/dist/components/layout/snice-layout-landing.js +21 -19
  63. package/dist/components/layout/snice-layout-landing.js.map +1 -1
  64. package/dist/components/layout/snice-layout-minimal.d.ts +2 -2
  65. package/dist/components/layout/snice-layout-minimal.js +17 -6
  66. package/dist/components/layout/snice-layout-minimal.js.map +1 -1
  67. package/dist/components/layout/snice-layout-sidebar.d.ts +5 -4
  68. package/dist/components/layout/snice-layout-sidebar.js +42 -20
  69. package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
  70. package/dist/components/layout/snice-layout-split.d.ts +2 -2
  71. package/dist/components/layout/snice-layout-split.js +14 -7
  72. package/dist/components/layout/snice-layout-split.js.map +1 -1
  73. package/dist/components/layout/snice-layout.d.ts +4 -4
  74. package/dist/components/layout/snice-layout.js +16 -10
  75. package/dist/components/layout/snice-layout.js.map +1 -1
  76. package/dist/components/login/snice-login.d.ts +6 -11
  77. package/dist/components/login/snice-login.js +97 -71
  78. package/dist/components/login/snice-login.js.map +1 -1
  79. package/dist/components/modal/snice-modal.d.ts +5 -9
  80. package/dist/components/modal/snice-modal.js +47 -78
  81. package/dist/components/modal/snice-modal.js.map +1 -1
  82. package/dist/components/nav/snice-nav.d.ts +13 -7
  83. package/dist/components/nav/snice-nav.js +191 -100
  84. package/dist/components/nav/snice-nav.js.map +1 -1
  85. package/dist/components/nav/snice-nav.types.d.ts +3 -3
  86. package/dist/components/pagination/snice-pagination.d.ts +6 -7
  87. package/dist/components/pagination/snice-pagination.js +94 -81
  88. package/dist/components/pagination/snice-pagination.js.map +1 -1
  89. package/dist/components/progress/snice-progress.d.ts +2 -7
  90. package/dist/components/progress/snice-progress.js +41 -98
  91. package/dist/components/progress/snice-progress.js.map +1 -1
  92. package/dist/components/radio/snice-radio.d.ts +4 -4
  93. package/dist/components/radio/snice-radio.js +52 -44
  94. package/dist/components/radio/snice-radio.js.map +1 -1
  95. package/dist/components/select/snice-option.d.ts +2 -1
  96. package/dist/components/select/snice-option.js +12 -5
  97. package/dist/components/select/snice-option.js.map +1 -1
  98. package/dist/components/select/snice-select.d.ts +9 -21
  99. package/dist/components/select/snice-select.js +98 -170
  100. package/dist/components/select/snice-select.js.map +1 -1
  101. package/dist/components/skeleton/snice-skeleton.d.ts +2 -6
  102. package/dist/components/skeleton/snice-skeleton.js +18 -49
  103. package/dist/components/skeleton/snice-skeleton.js.map +1 -1
  104. package/dist/components/snice-cell-BLFVdxPp.js +4 -0
  105. package/dist/components/snice-cell-BLFVdxPp.js.map +1 -0
  106. package/dist/components/switch/snice-switch.d.ts +2 -2
  107. package/dist/components/switch/snice-switch.js +38 -26
  108. package/dist/components/switch/snice-switch.js.map +1 -1
  109. package/dist/components/table/snice-cell-actions.d.ts +24 -0
  110. package/dist/components/table/snice-cell-actions.js +149 -0
  111. package/dist/components/table/snice-cell-actions.js.map +1 -0
  112. package/dist/components/table/snice-cell-boolean.d.ts +2 -2
  113. package/dist/components/table/snice-cell-boolean.js +13 -7
  114. package/dist/components/table/snice-cell-boolean.js.map +1 -1
  115. package/dist/components/table/snice-cell-color.d.ts +18 -0
  116. package/dist/components/table/snice-cell-color.js +149 -0
  117. package/dist/components/table/snice-cell-color.js.map +1 -0
  118. package/dist/components/table/snice-cell-currency.d.ts +24 -0
  119. package/dist/components/table/snice-cell-currency.js +235 -0
  120. package/dist/components/table/snice-cell-currency.js.map +1 -0
  121. package/dist/components/table/snice-cell-date.d.ts +2 -2
  122. package/dist/components/table/snice-cell-date.js +14 -8
  123. package/dist/components/table/snice-cell-date.js.map +1 -1
  124. package/dist/components/table/snice-cell-duration.d.ts +2 -2
  125. package/dist/components/table/snice-cell-duration.js +12 -6
  126. package/dist/components/table/snice-cell-duration.js.map +1 -1
  127. package/dist/components/table/snice-cell-email.d.ts +15 -0
  128. package/dist/components/table/snice-cell-email.js +125 -0
  129. package/dist/components/table/snice-cell-email.js.map +1 -0
  130. package/dist/components/table/snice-cell-filesize.d.ts +2 -2
  131. package/dist/components/table/snice-cell-filesize.js +12 -6
  132. package/dist/components/table/snice-cell-filesize.js.map +1 -1
  133. package/dist/components/table/snice-cell-image.d.ts +20 -0
  134. package/dist/components/table/snice-cell-image.js +162 -0
  135. package/dist/components/table/snice-cell-image.js.map +1 -0
  136. package/dist/components/table/snice-cell-json.d.ts +20 -0
  137. package/dist/components/table/snice-cell-json.js +186 -0
  138. package/dist/components/table/snice-cell-json.js.map +1 -0
  139. package/dist/components/table/snice-cell-link.d.ts +17 -0
  140. package/dist/components/table/snice-cell-link.js +142 -0
  141. package/dist/components/table/snice-cell-link.js.map +1 -0
  142. package/dist/components/table/snice-cell-location.d.ts +19 -0
  143. package/dist/components/table/snice-cell-location.js +185 -0
  144. package/dist/components/table/snice-cell-location.js.map +1 -0
  145. package/dist/components/table/snice-cell-number.d.ts +2 -2
  146. package/dist/components/table/snice-cell-number.js +12 -6
  147. package/dist/components/table/snice-cell-number.js.map +1 -1
  148. package/dist/components/table/snice-cell-percentage.d.ts +22 -0
  149. package/dist/components/table/snice-cell-percentage.js +208 -0
  150. package/dist/components/table/snice-cell-percentage.js.map +1 -0
  151. package/dist/components/table/snice-cell-phone.d.ts +18 -0
  152. package/dist/components/table/snice-cell-phone.js +153 -0
  153. package/dist/components/table/snice-cell-phone.js.map +1 -0
  154. package/dist/components/table/snice-cell-progress.d.ts +2 -2
  155. package/dist/components/table/snice-cell-progress.js +12 -6
  156. package/dist/components/table/snice-cell-progress.js.map +1 -1
  157. package/dist/components/table/snice-cell-rating.d.ts +2 -2
  158. package/dist/components/table/snice-cell-rating.js +12 -6
  159. package/dist/components/table/snice-cell-rating.js.map +1 -1
  160. package/dist/components/table/snice-cell-sparkline.d.ts +2 -2
  161. package/dist/components/table/snice-cell-sparkline.js +13 -7
  162. package/dist/components/table/snice-cell-sparkline.js.map +1 -1
  163. package/dist/components/table/snice-cell-status.d.ts +17 -0
  164. package/dist/components/table/snice-cell-status.js +144 -0
  165. package/dist/components/table/snice-cell-status.js.map +1 -0
  166. package/dist/components/table/snice-cell-tag.d.ts +16 -0
  167. package/dist/components/table/snice-cell-tag.js +131 -0
  168. package/dist/components/table/snice-cell-tag.js.map +1 -0
  169. package/dist/components/table/snice-cell-text.d.ts +2 -2
  170. package/dist/components/table/snice-cell-text.js +14 -8
  171. package/dist/components/table/snice-cell-text.js.map +1 -1
  172. package/dist/components/table/snice-cell.d.ts +2 -2
  173. package/dist/components/table/snice-cell.js +12 -6
  174. package/dist/components/table/snice-cell.js.map +1 -1
  175. package/dist/components/table/snice-column.d.ts +1 -1
  176. package/dist/components/table/snice-column.js +6 -3
  177. package/dist/components/table/snice-column.js.map +1 -1
  178. package/dist/components/table/snice-header.d.ts +5 -5
  179. package/dist/components/table/snice-header.js +60 -50
  180. package/dist/components/table/snice-header.js.map +1 -1
  181. package/dist/components/table/snice-progress.d.ts +2 -2
  182. package/dist/components/table/snice-progress.js +18 -11
  183. package/dist/components/table/snice-progress.js.map +1 -1
  184. package/dist/components/table/snice-rating.d.ts +2 -2
  185. package/dist/components/table/snice-rating.js +15 -8
  186. package/dist/components/table/snice-rating.js.map +1 -1
  187. package/dist/components/table/snice-row.d.ts +17 -6
  188. package/dist/components/table/snice-row.js +95 -44
  189. package/dist/components/table/snice-row.js.map +1 -1
  190. package/dist/components/table/snice-table.d.ts +18 -10
  191. package/dist/components/table/snice-table.js +355 -173
  192. package/dist/components/table/snice-table.js.map +1 -1
  193. package/dist/components/table/snice-table.types.d.ts +101 -2
  194. package/dist/components/tabs/snice-tab-panel.d.ts +2 -2
  195. package/dist/components/tabs/snice-tab-panel.js +12 -6
  196. package/dist/components/tabs/snice-tab-panel.js.map +1 -1
  197. package/dist/components/tabs/snice-tab.d.ts +6 -5
  198. package/dist/components/tabs/snice-tab.js +36 -19
  199. package/dist/components/tabs/snice-tab.js.map +1 -1
  200. package/dist/components/tabs/snice-tabs.d.ts +5 -5
  201. package/dist/components/tabs/snice-tabs.js +38 -28
  202. package/dist/components/tabs/snice-tabs.js.map +1 -1
  203. package/dist/components/toast/snice-toast-container.d.ts +7 -7
  204. package/dist/components/toast/snice-toast-container.js +19 -12
  205. package/dist/components/toast/snice-toast-container.js.map +1 -1
  206. package/dist/components/toast/snice-toast.d.ts +3 -15
  207. package/dist/components/toast/snice-toast.js +49 -108
  208. package/dist/components/toast/snice-toast.js.map +1 -1
  209. package/dist/components/tooltip/snice-tooltip.d.ts +2 -2
  210. package/dist/components/tooltip/snice-tooltip.js +14 -7
  211. package/dist/components/tooltip/snice-tooltip.js.map +1 -1
  212. package/dist/context.d.ts +44 -0
  213. package/dist/element-ready.d.ts +40 -0
  214. package/dist/{types/element.d.ts → element.d.ts} +2 -8
  215. package/dist/{types/events.d.ts → events.d.ts} +0 -4
  216. package/dist/index.cjs +2589 -605
  217. package/dist/index.cjs.map +1 -1
  218. package/dist/index.d.ts +21 -0
  219. package/dist/index.esm.js +2568 -604
  220. package/dist/index.esm.js.map +1 -1
  221. package/dist/index.iife.js +2589 -605
  222. package/dist/index.iife.js.map +1 -1
  223. package/dist/method-decorators.d.ts +121 -0
  224. package/dist/on.d.ts +59 -0
  225. package/dist/parts.d.ts +159 -0
  226. package/dist/render-debug.d.ts +27 -0
  227. package/dist/render-tracker.d.ts +14 -0
  228. package/dist/render.d.ts +96 -0
  229. package/dist/symbols.cjs +163 -0
  230. package/dist/symbols.cjs.map +1 -1
  231. package/dist/{types/symbols.d.ts → symbols.d.ts} +22 -0
  232. package/dist/symbols.esm.js +27 -3
  233. package/dist/symbols.esm.js.map +1 -1
  234. package/dist/template.d.ts +100 -0
  235. package/dist/transitions.cjs +219 -0
  236. package/dist/transitions.esm.js +2 -2
  237. package/dist/types/context.d.ts +48 -0
  238. package/dist/types/element-options.d.ts +26 -0
  239. package/dist/types/index.d.ts +25 -9
  240. package/dist/types/nav-context.d.ts +19 -0
  241. package/dist/types/{types/on-options.d.ts → on-options.d.ts} +2 -0
  242. package/dist/types/{types/placard.d.ts → placard.d.ts} +0 -1
  243. package/docs/ai/README.md +17 -0
  244. package/docs/ai/api.md +175 -0
  245. package/docs/ai/architecture.md +160 -0
  246. package/docs/ai/components/accordion.md +174 -0
  247. package/docs/ai/components/alert.md +77 -0
  248. package/docs/ai/components/avatar.md +61 -0
  249. package/docs/ai/components/badge.md +69 -0
  250. package/docs/ai/components/breadcrumbs.md +74 -0
  251. package/docs/ai/components/button.md +75 -0
  252. package/docs/ai/components/card.md +61 -0
  253. package/docs/ai/components/checkbox.md +74 -0
  254. package/docs/ai/components/chip.md +73 -0
  255. package/docs/ai/components/date-picker.md +75 -0
  256. package/docs/ai/components/divider.md +66 -0
  257. package/docs/ai/components/drawer.md +80 -0
  258. package/docs/ai/components/input.md +111 -0
  259. package/docs/ai/components/login.md +109 -0
  260. package/docs/ai/components/modal.md +67 -0
  261. package/docs/ai/components/nav.md +76 -0
  262. package/docs/ai/components/pagination.md +55 -0
  263. package/docs/ai/components/progress.md +72 -0
  264. package/docs/ai/components/radio.md +79 -0
  265. package/docs/ai/components/select.md +92 -0
  266. package/docs/ai/components/skeleton.md +57 -0
  267. package/docs/ai/components/switch.md +53 -0
  268. package/docs/ai/components/table.md +227 -0
  269. package/docs/ai/components/tabs.md +83 -0
  270. package/docs/ai/components/toast.md +140 -0
  271. package/docs/ai/components/tooltip.md +146 -0
  272. package/docs/ai/patterns.md +244 -0
  273. package/docs/components/accordion.md +558 -0
  274. package/docs/components/drawer.md +602 -0
  275. package/docs/components/modal.md +558 -0
  276. package/docs/components/nav.md +239 -0
  277. package/docs/components/pagination.md +289 -0
  278. package/docs/components/select.md +599 -0
  279. package/docs/components/switch.md +354 -0
  280. package/docs/components/tabs.md +546 -0
  281. package/docs/components/toast.md +506 -0
  282. package/docs/components/tooltip.md +523 -0
  283. package/docs/controllers.md +744 -0
  284. package/docs/elements.md +855 -0
  285. package/docs/events.md +807 -0
  286. package/docs/migration-v2-to-v3.md +569 -0
  287. package/docs/observe.md +588 -0
  288. package/docs/placards.md +401 -0
  289. package/docs/request-response.md +852 -0
  290. package/docs/routing.md +1186 -0
  291. package/package.json +10 -11
  292. package/dist/components/snice-cell-C9N6yGxQ.js +0 -4
  293. package/dist/components/snice-cell-C9N6yGxQ.js.map +0 -1
  294. package/dist/types/types/index.d.ts +0 -23
  295. /package/dist/{types/controller.d.ts → controller.d.ts} +0 -0
  296. /package/dist/{types/global.d.ts → global.d.ts} +0 -0
  297. /package/dist/{types/observe.d.ts → observe.d.ts} +0 -0
  298. /package/dist/{types/request-response.d.ts → request-response.d.ts} +0 -0
  299. /package/dist/{types/router.d.ts → router.d.ts} +0 -0
  300. /package/dist/{types/testing.d.ts → testing.d.ts} +0 -0
  301. /package/dist/{types/transitions.d.ts → transitions.d.ts} +0 -0
  302. /package/dist/types/{types/adopted-options.d.ts → adopted-options.d.ts} +0 -0
  303. /package/dist/types/{types/app-context.d.ts → app-context.d.ts} +0 -0
  304. /package/dist/types/{types/dispatch-options.d.ts → dispatch-options.d.ts} +0 -0
  305. /package/dist/types/{types/guard.d.ts → guard.d.ts} +0 -0
  306. /package/dist/types/{types/i-controller.d.ts → i-controller.d.ts} +0 -0
  307. /package/dist/types/{types/moved-options.d.ts → moved-options.d.ts} +0 -0
  308. /package/dist/types/{types/observe-options.d.ts → observe-options.d.ts} +0 -0
  309. /package/dist/types/{types/page-options.d.ts → page-options.d.ts} +0 -0
  310. /package/dist/types/{types/part-options.d.ts → part-options.d.ts} +0 -0
  311. /package/dist/types/{types/property-converter.d.ts → property-converter.d.ts} +0 -0
  312. /package/dist/types/{types/property-options.d.ts → property-options.d.ts} +0 -0
  313. /package/dist/types/{types/query-options.d.ts → query-options.d.ts} +0 -0
  314. /package/dist/types/{types/request-options.d.ts → request-options.d.ts} +0 -0
  315. /package/dist/types/{types/respond-options.d.ts → respond-options.d.ts} +0 -0
  316. /package/dist/types/{types/route-params.d.ts → route-params.d.ts} +0 -0
  317. /package/dist/types/{types/router-instance.d.ts → router-instance.d.ts} +0 -0
  318. /package/dist/types/{types/router-options.d.ts → router-options.d.ts} +0 -0
  319. /package/dist/types/{types/simple-array.d.ts → simple-array.d.ts} +0 -0
  320. /package/dist/types/{types/snice-element.d.ts → snice-element.d.ts} +0 -0
  321. /package/dist/types/{types/snice-global.d.ts → snice-global.d.ts} +0 -0
  322. /package/dist/types/{types/transition.d.ts → transition.d.ts} +0 -0
  323. /package/dist/{types/utils.d.ts → utils.d.ts} +0 -0
@@ -0,0 +1,852 @@
1
+ # Request/Response API Documentation
2
+
3
+ Request/Response provides bidirectional request/response communication between elements and controllers using async generators.
4
+
5
+ ## Table of Contents
6
+ - [Basic Concept](#basic-concept)
7
+ - [Request/Response Decorators](#requestresponse-decorators)
8
+ - [Element-Side Requests](#element-side-requests)
9
+ - [Controller-Side Responses](#controller-side-responses)
10
+ - [Request/Response Options](#requestresponse-options)
11
+ - [Error Handling](#error-handling)
12
+ - [Advanced Patterns](#advanced-patterns)
13
+
14
+ ## Basic Concept
15
+
16
+ Request/Response enables a request/response pattern between elements and their controllers:
17
+
18
+ 1. **Element** sends a request using `yield`
19
+ 2. **Controller** receives the request and returns a response
20
+ 3. **Element** receives the response and can process it
21
+
22
+ This pattern is implemented using async generators and custom events.
23
+
24
+ ## Request/Response Decorators
25
+
26
+ ### Signature
27
+
28
+ ```typescript
29
+ function request(requestName: string, options?: RequestOptions): MethodDecorator
30
+ function response(responseName: string, options?: RespondOptions): MethodDecorator
31
+
32
+ interface RequestOptions extends EventInit {
33
+ timeout?: number; // Response timeout in ms (default: 120000ms = 2 minutes)
34
+ discoveryTimeout?: number; // Handler discovery timeout in ms (default: 50ms)
35
+ debounce?: number; // Debounce requests by specified ms
36
+ throttle?: number; // Throttle requests by specified ms
37
+ }
38
+
39
+ interface RespondOptions {
40
+ debounce?: number; // Debounce responses by specified ms
41
+ throttle?: number; // Throttle responses by specified ms
42
+ }
43
+
44
+ // Type helper to eliminate TypeScript warnings in request generators
45
+ // Use this for method signatures - it handles both the generator implementation and promise return
46
+ type Response<T> = AsyncGenerator<any, T, any> | Promise<T>;
47
+ ```
48
+
49
+ #### Response Debounce/Throttle
50
+
51
+ Response handlers can also be debounced or throttled to prevent excessive processing:
52
+
53
+ ```typescript
54
+ @controller('heavy-processing-controller')
55
+ class ProcessingController implements IController {
56
+
57
+ @respond('@api/expensive-calculation', { debounce: 1000 })
58
+ async calculateResults(params: any) {
59
+ // Debounced by 1 second - rapid requests will only trigger the latest
60
+ return await performExpensiveCalculation(params);
61
+ }
62
+
63
+ @respond('@api/real-time-updates', { throttle: 500 })
64
+ async handleUpdates(data: any) {
65
+ // Throttled to max 2 requests per second
66
+ return await processUpdate(data);
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Element-Side Requests
72
+
73
+ Elements use async generators to make requests:
74
+
75
+ ```typescript
76
+ import { element, request, Response, render, html } from 'snice';
77
+
78
+ @element('user-card')
79
+ class UserCard extends HTMLElement {
80
+ userId = 123;
81
+
82
+ @render()
83
+ renderContent() {
84
+ return html`
85
+ <div class="user-info">
86
+ <button @click=${this.loadUser}>Load User</button>
87
+ <div class="content"></div>
88
+ </div>
89
+ `;
90
+ }
91
+
92
+ @request('fetch-user')
93
+ async *fetchUserData(): Response<{ success: boolean; user: any }> {
94
+ // Yield sends the request, await waits for response
95
+ const user = await (yield { userId: this.userId });
96
+
97
+ // Process the response
98
+ this.displayUser(user);
99
+
100
+ // Return final value (optional)
101
+ return { success: true, user };
102
+ }
103
+
104
+ async loadUser() {
105
+ try {
106
+ const result = await this.fetchUserData();
107
+ console.log('Load complete:', result);
108
+ } catch (error) {
109
+ console.error('Failed to load user:', error);
110
+ }
111
+ }
112
+
113
+ displayUser(user: any) {
114
+ const content = this.shadowRoot?.querySelector('.content');
115
+ if (content) {
116
+ content.innerHTML = `
117
+ <h3>${user.name}</h3>
118
+ <p>${user.email}</p>
119
+ `;
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Multiple Yields
126
+
127
+ Elements can yield multiple times in a single channel:
128
+
129
+ ```typescript
130
+ @element('multi-request')
131
+ class MultiRequest extends HTMLElement {
132
+ @request('multi-data')
133
+ async *fetchMultipleData() {
134
+ // First request
135
+ const userData = await (yield { type: 'user', id: 1 });
136
+ console.log('Got user:', userData);
137
+
138
+ // Second request based on first response
139
+ const postsData = await (yield { type: 'posts', userId: userData.id });
140
+ console.log('Got posts:', postsData);
141
+
142
+ // Third request
143
+ const commentsData = await (yield { type: 'comments', postIds: postsData.map((p: any) => p.id) });
144
+ console.log('Got comments:', commentsData);
145
+
146
+ // Return combined result
147
+ return {
148
+ user: userData,
149
+ posts: postsData,
150
+ comments: commentsData
151
+ };
152
+ }
153
+
154
+ @render()
155
+ renderContent() {
156
+ return html`<button @click=${this.fetchData}>Fetch All Data</button>`;
157
+ }
158
+
159
+ async fetchData() {
160
+ const result = await this.fetchMultipleData();
161
+ console.log('All data loaded:', result);
162
+ }
163
+ }
164
+ ```
165
+
166
+ ## Controller-Side Responses
167
+
168
+ Controllers handle requests and provide responses:
169
+
170
+ ```typescript
171
+ import { controller, respond, IController } from 'snice';
172
+
173
+ @controller('user-controller')
174
+ class UserController implements IController {
175
+ element: HTMLElement | null = null;
176
+
177
+ async attach(element: HTMLElement) {
178
+ console.log('User controller attached');
179
+ }
180
+
181
+ async detach(element: HTMLElement) {
182
+ console.log('User controller detached');
183
+ }
184
+
185
+ @respond('fetch-user')
186
+ async handleFetchUser(request: { userId: number }) {
187
+ // Simulate API call
188
+ const response = await fetch(`/api/users/${request.userId}`);
189
+ const user = await response.json();
190
+
191
+ // Return response to element
192
+ return user;
193
+ }
194
+
195
+ @respond('multi-data')
196
+ async handleMultiData(request: any) {
197
+ switch (request.type) {
198
+ case 'user':
199
+ return await this.fetchUser(request.id);
200
+ case 'posts':
201
+ return await this.fetchPosts(request.userId);
202
+ case 'comments':
203
+ return await this.fetchComments(request.postIds);
204
+ default:
205
+ throw new Error(`Unknown request type: ${request.type}`);
206
+ }
207
+ }
208
+
209
+ private async fetchUser(id: number) {
210
+ // Simulate API call
211
+ return { id, name: 'John Doe', email: 'john@example.com' };
212
+ }
213
+
214
+ private async fetchPosts(userId: number) {
215
+ // Simulate API call
216
+ return [
217
+ { id: 1, userId, title: 'Post 1' },
218
+ { id: 2, userId, title: 'Post 2' }
219
+ ];
220
+ }
221
+
222
+ private async fetchComments(postIds: number[]) {
223
+ // Simulate API call
224
+ return postIds.flatMap(postId => [
225
+ { id: 1, postId, text: 'Comment 1' },
226
+ { id: 2, postId, text: 'Comment 2' }
227
+ ]);
228
+ }
229
+ }
230
+ ```
231
+
232
+ ## Request/Response Options
233
+
234
+ ### RequestOptions
235
+
236
+ ```typescript
237
+ interface RequestOptions extends EventInit {
238
+ timeout?: number; // Response timeout in ms (default: 120000ms = 2 minutes)
239
+ discoveryTimeout?: number; // Handler discovery timeout in ms (default: 50ms)
240
+ debounce?: number; // Debounce requests by specified ms
241
+ throttle?: number; // Throttle requests by specified ms
242
+ }
243
+ ```
244
+
245
+ #### Timeout Behavior (IMPORTANT)
246
+
247
+ The timeout system has **two separate timeouts** for different phases:
248
+
249
+ - **Discovery timeout** (`discoveryTimeout`): 50ms (default) - Fast timeout to find a handler
250
+ - **Response timeout** (`timeout`): 2 minutes (default) - Total time allowed for the request
251
+
252
+ ```typescript
253
+ @request('@api/heavy-processing', {
254
+ discoveryTimeout: 50, // 50ms to find handler (fast)
255
+ timeout: 30000 // 30s total timeout for processing
256
+ })
257
+ async *processData() {
258
+ // Will timeout in 50ms if no handler exists
259
+ // Will timeout in 30s total if processing takes too long
260
+ const result = await (yield data);
261
+ return result;
262
+ }
263
+ ```
264
+
265
+ **Why two timeouts?**
266
+ - **Discovery**: Should be very fast (dozens of milliseconds) - just finding if anyone can handle the request
267
+ - **Response**: Should be human-scale (seconds/minutes) - actual work takes time
268
+
269
+ #### Debounce Support
270
+
271
+ Prevents rapid successive requests by delaying execution:
272
+
273
+ ```typescript
274
+ @request('@api/search', { debounce: 300 })
275
+ async *search() {
276
+ // Debounced by 300ms - rapid calls will cancel previous ones
277
+ const results = await (yield query);
278
+ return results;
279
+ }
280
+ ```
281
+
282
+ #### Throttle Support
283
+
284
+ Limits request frequency to maximum rate:
285
+
286
+ ```typescript
287
+ @request('@api/analytics', { throttle: 1000 })
288
+ async *trackEvent() {
289
+ // Throttled to max 1 request per second
290
+ const response = await (yield eventData);
291
+ return response;
292
+ }
293
+ ```
294
+
295
+ ### Timeout Configuration
296
+
297
+ ```typescript
298
+ @element('timeout-example')
299
+ class TimeoutExample extends HTMLElement {
300
+ // Quick discovery, short total timeout for fast operations
301
+ @request('quick-data', {
302
+ discoveryTimeout: 25, // Very fast discovery
303
+ timeout: 1000 // 1 second total
304
+ })
305
+ async *fetchQuickData() {
306
+ const data = await (yield { quick: true });
307
+ return data;
308
+ }
309
+
310
+ // Standard discovery, longer timeout for slow operations
311
+ @request('slow-data', {
312
+ discoveryTimeout: 50, // Default discovery
313
+ timeout: 30000 // 30 seconds total
314
+ })
315
+ async *fetchSlowData() {
316
+ const data = await (yield { slow: true });
317
+ return data;
318
+ }
319
+
320
+ // Use defaults (50ms discovery, 2 minutes total)
321
+ @request('default-data')
322
+ async *fetchDefaultData() {
323
+ const data = await (yield { default: true });
324
+ return data;
325
+ }
326
+
327
+ // Custom event options with timeouts
328
+ @request('private-data', {
329
+ discoveryTimeout: 100, // Slower discovery
330
+ timeout: 60000, // 1 minute total
331
+ bubbles: false, // Don't bubble
332
+ cancelable: true // Can be canceled
333
+ })
334
+ async *fetchPrivateData() {
335
+ const data = await (yield { private: true });
336
+ return data;
337
+ }
338
+
339
+ @render()
340
+ renderContent() {
341
+ return html`<div>Timeout examples</div>`;
342
+ }
343
+ }
344
+ ```
345
+
346
+ ## Error Handling
347
+
348
+ ### Handling Timeouts
349
+
350
+ ```typescript
351
+ @element('timeout-handler')
352
+ class TimeoutHandler extends HTMLElement {
353
+ @request('data', {
354
+ discoveryTimeout: 50,
355
+ timeout: 5000
356
+ })
357
+ async *fetchData() {
358
+ try {
359
+ const data = await (yield { request: 'data' });
360
+ return { success: true, data };
361
+ } catch (error: any) {
362
+ // Handle different types of timeout errors
363
+ if (error.message.includes('timed out after') && error.message.includes('no handler found')) {
364
+ console.error('No handler found for request');
365
+ return { success: false, error: 'no_handler' };
366
+ } else if (error.message.includes('timed out after')) {
367
+ console.error('Request processing timed out');
368
+ return { success: false, error: 'timeout' };
369
+ }
370
+ throw error;
371
+ }
372
+ }
373
+
374
+ async loadData() {
375
+ try {
376
+ const result = await this.fetchData();
377
+ if (!result.success) {
378
+ this.showError('Failed to load data');
379
+ }
380
+ } catch (error) {
381
+ this.showError('Unexpected error');
382
+ }
383
+ }
384
+
385
+ showError(message: string) {
386
+ console.error(message);
387
+ }
388
+
389
+ @render()
390
+ renderContent() {
391
+ return html`<button @click=${this.loadData}>Load Data</button>`;
392
+ }
393
+ }
394
+ ```
395
+
396
+ ### Controller Error Handling
397
+
398
+ ```typescript
399
+ @controller('error-controller')
400
+ class ErrorController implements IController {
401
+ element: HTMLElement | null = null;
402
+
403
+ async attach(element: HTMLElement) {}
404
+ async detach(element: HTMLElement) {}
405
+
406
+ @respond('risky-operation')
407
+ async handleRiskyOperation(request: any) {
408
+ try {
409
+ // Validate request
410
+ if (!request.id) {
411
+ throw new Error('ID is required');
412
+ }
413
+
414
+ // Perform operation
415
+ const result = await this.performOperation(request.id);
416
+
417
+ return { success: true, result };
418
+ } catch (error: any) {
419
+ // Return error info instead of throwing
420
+ return {
421
+ success: false,
422
+ error: error.message,
423
+ code: error.code || 'UNKNOWN_ERROR'
424
+ };
425
+ }
426
+ }
427
+
428
+ private async performOperation(id: string) {
429
+ // Simulate operation that might fail
430
+ if (Math.random() > 0.5) {
431
+ throw new Error('Random failure');
432
+ }
433
+ return { id, processed: true };
434
+ }
435
+ }
436
+ ```
437
+
438
+ ## Advanced Patterns
439
+
440
+ ### Authentication Request/Response
441
+
442
+ ```typescript
443
+ // Element side
444
+ @element('protected-content')
445
+ class ProtectedContent extends HTMLElement {
446
+ @request('authenticate')
447
+ async *authenticate() {
448
+ // Send credentials
449
+ const authResult = await (yield {
450
+ username: 'user@example.com',
451
+ password: 'secret'
452
+ });
453
+
454
+ if (!authResult.success) {
455
+ throw new Error(authResult.error);
456
+ }
457
+
458
+ // Store token
459
+ localStorage.setItem('token', authResult.token);
460
+
461
+ return authResult;
462
+ }
463
+
464
+ @request('fetch-protected')
465
+ async *fetchProtectedData() {
466
+ const token = localStorage.getItem('token');
467
+
468
+ if (!token) {
469
+ // Need to authenticate first
470
+ await this.authenticate();
471
+ }
472
+
473
+ // Fetch with token
474
+ const data = await (yield {
475
+ resource: 'protected',
476
+ token: localStorage.getItem('token')
477
+ });
478
+
479
+ return data;
480
+ }
481
+
482
+ @render()
483
+ renderContent() {
484
+ return html`<button @click=${this.loadProtected}>Load Protected Data</button>`;
485
+ }
486
+
487
+ async loadProtected() {
488
+ try {
489
+ const data = await this.fetchProtectedData();
490
+ console.log('Protected data:', data);
491
+ } catch (error) {
492
+ console.error('Failed to load:', error);
493
+ }
494
+ }
495
+ }
496
+
497
+ // Controller side
498
+ @controller('auth-controller')
499
+ class AuthController implements IController {
500
+ element: HTMLElement | null = null;
501
+ private tokens = new Map<string, any>();
502
+
503
+ async attach(element: HTMLElement) {}
504
+ async detach(element: HTMLElement) {}
505
+
506
+ @respond('authenticate')
507
+ async handleAuth(credentials: any) {
508
+ // Validate credentials
509
+ if (credentials.username === 'user@example.com' &&
510
+ credentials.password === 'secret') {
511
+
512
+ const token = this.generateToken();
513
+ const user = { id: 1, name: 'User' };
514
+
515
+ this.tokens.set(token, user);
516
+
517
+ return {
518
+ success: true,
519
+ token,
520
+ user
521
+ };
522
+ }
523
+
524
+ return {
525
+ success: false,
526
+ error: 'Invalid credentials'
527
+ };
528
+ }
529
+
530
+ @respond('fetch-protected')
531
+ async handleFetchProtected(request: any) {
532
+ // Validate token
533
+ const user = this.tokens.get(request.token);
534
+
535
+ if (!user) {
536
+ throw new Error('Invalid or expired token');
537
+ }
538
+
539
+ // Return protected data
540
+ return {
541
+ resource: request.resource,
542
+ data: { secret: 'Protected information' },
543
+ user
544
+ };
545
+ }
546
+
547
+ private generateToken() {
548
+ return Math.random().toString(36).substring(2);
549
+ }
550
+ }
551
+ ```
552
+
553
+ ### Streaming Data Request/Response
554
+
555
+ ```typescript
556
+ // Element side
557
+ @element('data-streamer')
558
+ class DataStreamer extends HTMLElement {
559
+ private items: any[] = [];
560
+
561
+ @request('stream-data')
562
+ async *streamData() {
563
+ let hasMore = true;
564
+ let page = 1;
565
+
566
+ while (hasMore) {
567
+ // Request next page
568
+ const response = await (yield {
569
+ page,
570
+ pageSize: 10
571
+ });
572
+
573
+ // Add items to list
574
+ this.items.push(...response.items);
575
+ this.renderItems();
576
+
577
+ // Check if more pages available
578
+ hasMore = response.hasMore;
579
+ page++;
580
+ }
581
+
582
+ return {
583
+ totalItems: this.items.length,
584
+ complete: true
585
+ };
586
+ }
587
+
588
+ renderItems() {
589
+ const container = this.shadowRoot?.querySelector('.items');
590
+ if (container) {
591
+ container.innerHTML = this.items
592
+ .map(item => `<div>${item.name}</div>`)
593
+ .join('');
594
+ }
595
+ }
596
+
597
+ @render()
598
+ renderContent() {
599
+ return html`
600
+ <button @click=${this.loadAllData}>Load All Data</button>
601
+ <div class="items"></div>
602
+ `;
603
+ }
604
+
605
+ async loadAllData() {
606
+ const result = await this.streamData();
607
+ console.log(`Loaded ${result.totalItems} items`);
608
+ }
609
+ }
610
+
611
+ // Controller side
612
+ @controller('stream-controller')
613
+ class StreamController implements IController {
614
+ element: HTMLElement | null = null;
615
+ private allData: any[] = Array.from({ length: 35 }, (_, i) => ({
616
+ id: i + 1,
617
+ name: `Item ${i + 1}`
618
+ }));
619
+
620
+ async attach(element: HTMLElement) {}
621
+ async detach(element: HTMLElement) {}
622
+
623
+ @respond('stream-data')
624
+ async handleStreamData(request: { page: number; pageSize: number }) {
625
+ // Simulate delay
626
+ await new Promise(resolve => setTimeout(resolve, 500));
627
+
628
+ // Calculate pagination
629
+ const start = (request.page - 1) * request.pageSize;
630
+ const end = start + request.pageSize;
631
+
632
+ const items = this.allData.slice(start, end);
633
+ const hasMore = end < this.allData.length;
634
+
635
+ return {
636
+ items,
637
+ hasMore,
638
+ page: request.page,
639
+ totalPages: Math.ceil(this.allData.length / request.pageSize)
640
+ };
641
+ }
642
+ }
643
+ ```
644
+
645
+ ### Cached Request/Response
646
+
647
+ ```typescript
648
+ @controller('cached-controller')
649
+ class CachedController implements IController {
650
+ element: HTMLElement | null = null;
651
+ private cache = new Map<string, { data: any; timestamp: number }>();
652
+ private cacheTimeout = 60000; // 1 minute
653
+
654
+ async attach(element: HTMLElement) {}
655
+ async detach(element: HTMLElement) {}
656
+
657
+ @respond('fetch-cached')
658
+ async handleFetchCached(request: { key: string; forceRefresh?: boolean }) {
659
+ const cacheKey = request.key;
660
+ const cached = this.cache.get(cacheKey);
661
+
662
+ // Check cache validity
663
+ if (!request.forceRefresh && cached) {
664
+ const age = Date.now() - cached.timestamp;
665
+ if (age < this.cacheTimeout) {
666
+ console.log(`Returning cached data for ${cacheKey}`);
667
+ return {
668
+ data: cached.data,
669
+ fromCache: true,
670
+ age
671
+ };
672
+ }
673
+ }
674
+
675
+ // Fetch fresh data
676
+ console.log(`Fetching fresh data for ${cacheKey}`);
677
+ const freshData = await this.fetchFreshData(cacheKey);
678
+
679
+ // Update cache
680
+ this.cache.set(cacheKey, {
681
+ data: freshData,
682
+ timestamp: Date.now()
683
+ });
684
+
685
+ return {
686
+ data: freshData,
687
+ fromCache: false,
688
+ age: 0
689
+ };
690
+ }
691
+
692
+ private async fetchFreshData(key: string) {
693
+ // Simulate API call
694
+ await new Promise(resolve => setTimeout(resolve, 1000));
695
+ return { key, value: Math.random(), timestamp: Date.now() };
696
+ }
697
+ }
698
+ ```
699
+
700
+ ### Bidirectional Updates
701
+
702
+ ```typescript
703
+ // Element that can both request and be updated
704
+ import { element, property, query, request, watch, render, html } from 'snice';
705
+
706
+ @element('live-data')
707
+ class LiveData extends HTMLElement {
708
+ private updateInterval?: number;
709
+
710
+ @property()
711
+ status = 'Disconnected';
712
+
713
+ @query('.status')
714
+ statusDiv?: HTMLElement;
715
+
716
+ @query('.data')
717
+ dataDiv?: HTMLElement;
718
+
719
+ @render()
720
+ renderContent() {
721
+ return html`
722
+ <div class="status">${this.status}</div>
723
+ <div class="data"></div>
724
+ <button @click=${this.connect}>Connect</button>
725
+ <button @click=${this.disconnect}>Disconnect</button>
726
+ `;
727
+ }
728
+
729
+ @request('subscribe')
730
+ async *subscribe() {
731
+ // Send subscription request
732
+ const subscription = await (yield {
733
+ subscribe: true,
734
+ events: ['update', 'status']
735
+ });
736
+
737
+ if (subscription.success) {
738
+ this.status = 'Connected'; // @watch will handle UI update
739
+
740
+ // Start polling for updates
741
+ this.startPolling();
742
+ }
743
+
744
+ return subscription;
745
+ }
746
+
747
+ @request('poll-updates')
748
+ async *pollForUpdates() {
749
+ const updates = await (yield { poll: true });
750
+
751
+ if (updates && updates.length > 0) {
752
+ this.processUpdates(updates);
753
+ }
754
+
755
+ return { processed: updates.length };
756
+ }
757
+
758
+ async connect() {
759
+ await this.subscribe();
760
+ }
761
+
762
+ disconnect() {
763
+ this.stopPolling();
764
+ this.status = 'Disconnected';
765
+ }
766
+
767
+ startPolling() {
768
+ this.updateInterval = setInterval(async () => {
769
+ await this.pollForUpdates();
770
+ }, 2000);
771
+ }
772
+
773
+ stopPolling() {
774
+ if (this.updateInterval) {
775
+ clearInterval(this.updateInterval);
776
+ this.updateInterval = undefined;
777
+ }
778
+ }
779
+
780
+ processUpdates(updates: any[]) {
781
+ if (this.dataDiv) {
782
+ updates.forEach(update => {
783
+ const entry = document.createElement('div');
784
+ entry.textContent = `${update.type}: ${update.value}`;
785
+ this.dataDiv!.appendChild(entry);
786
+ });
787
+ }
788
+ }
789
+
790
+ @watch('status')
791
+ updateStatus() {
792
+ if (this.statusDiv) {
793
+ this.statusDiv.textContent = this.status;
794
+ }
795
+ }
796
+
797
+ disconnectedCallback() {
798
+ super.disconnectedCallback?.();
799
+ this.stopPolling();
800
+ }
801
+ }
802
+
803
+ // Controller that manages subscriptions
804
+ @controller('subscription-controller')
805
+ class SubscriptionController implements IController {
806
+ element: HTMLElement | null = null;
807
+ private subscribers = new Set<string>();
808
+ private updates: any[] = [];
809
+
810
+ async attach(element: HTMLElement) {
811
+ // Generate updates periodically
812
+ setInterval(() => {
813
+ this.updates.push({
814
+ type: 'update',
815
+ value: Math.random(),
816
+ timestamp: Date.now()
817
+ });
818
+ }, 3000);
819
+ }
820
+
821
+ async detach(element: HTMLElement) {
822
+ this.subscribers.clear();
823
+ }
824
+
825
+ @respond('subscribe')
826
+ handleSubscribe(request: any) {
827
+ if (request.subscribe) {
828
+ const id = Math.random().toString(36);
829
+ this.subscribers.add(id);
830
+
831
+ return {
832
+ success: true,
833
+ subscriptionId: id,
834
+ events: request.events
835
+ };
836
+ }
837
+
838
+ return { success: false };
839
+ }
840
+
841
+ @respond('poll-updates')
842
+ handlePollUpdates(request: any) {
843
+ if (!request.poll) return [];
844
+
845
+ // Return and clear updates
846
+ const updates = [...this.updates];
847
+ this.updates = [];
848
+
849
+ return updates;
850
+ }
851
+ }
852
+ ```