@qwickapps/server 1.3.1 → 1.5.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 (395) hide show
  1. package/README.md +157 -0
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +114 -0
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/types.d.ts +19 -0
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +15 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
  12. package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
  13. package/dist/plugins/auth/adapter-wrapper.js +166 -0
  14. package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
  15. package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
  16. package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
  17. package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
  18. package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
  19. package/dist/plugins/auth/config-store.d.ts +11 -0
  20. package/dist/plugins/auth/config-store.d.ts.map +1 -0
  21. package/dist/plugins/auth/config-store.js +232 -0
  22. package/dist/plugins/auth/config-store.js.map +1 -0
  23. package/dist/plugins/auth/config-store.test.d.ts +7 -0
  24. package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
  25. package/dist/plugins/auth/config-store.test.js +299 -0
  26. package/dist/plugins/auth/config-store.test.js.map +1 -0
  27. package/dist/plugins/auth/env-config.d.ts +51 -1
  28. package/dist/plugins/auth/env-config.d.ts.map +1 -1
  29. package/dist/plugins/auth/env-config.js +640 -7
  30. package/dist/plugins/auth/env-config.js.map +1 -1
  31. package/dist/plugins/auth/index.d.ts +6 -2
  32. package/dist/plugins/auth/index.d.ts.map +1 -1
  33. package/dist/plugins/auth/index.js +5 -1
  34. package/dist/plugins/auth/index.js.map +1 -1
  35. package/dist/plugins/auth/types.d.ts +106 -0
  36. package/dist/plugins/auth/types.d.ts.map +1 -1
  37. package/dist/plugins/bans/bans-plugin.d.ts.map +1 -1
  38. package/dist/plugins/bans/bans-plugin.js +12 -3
  39. package/dist/plugins/bans/bans-plugin.js.map +1 -1
  40. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts +11 -0
  41. package/dist/plugins/devices/__tests__/devices-plugin.test.d.ts.map +1 -0
  42. package/dist/plugins/devices/__tests__/devices-plugin.test.js +410 -0
  43. package/dist/plugins/devices/__tests__/devices-plugin.test.js.map +1 -0
  44. package/dist/plugins/devices/__tests__/token-utils.test.d.ts +7 -0
  45. package/dist/plugins/devices/__tests__/token-utils.test.d.ts.map +1 -0
  46. package/dist/plugins/devices/__tests__/token-utils.test.js +197 -0
  47. package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -0
  48. package/dist/plugins/devices/adapters/compute-adapter.d.ts +36 -0
  49. package/dist/plugins/devices/adapters/compute-adapter.d.ts.map +1 -0
  50. package/dist/plugins/devices/adapters/compute-adapter.js +100 -0
  51. package/dist/plugins/devices/adapters/compute-adapter.js.map +1 -0
  52. package/dist/plugins/devices/adapters/index.d.ts +12 -0
  53. package/dist/plugins/devices/adapters/index.d.ts.map +1 -0
  54. package/dist/plugins/devices/adapters/index.js +10 -0
  55. package/dist/plugins/devices/adapters/index.js.map +1 -0
  56. package/dist/plugins/devices/adapters/mobile-adapter.d.ts +41 -0
  57. package/dist/plugins/devices/adapters/mobile-adapter.d.ts.map +1 -0
  58. package/dist/plugins/devices/adapters/mobile-adapter.js +131 -0
  59. package/dist/plugins/devices/adapters/mobile-adapter.js.map +1 -0
  60. package/dist/plugins/devices/devices-plugin.d.ts +70 -0
  61. package/dist/plugins/devices/devices-plugin.d.ts.map +1 -0
  62. package/dist/plugins/devices/devices-plugin.js +453 -0
  63. package/dist/plugins/devices/devices-plugin.js.map +1 -0
  64. package/dist/plugins/devices/index.d.ts +18 -0
  65. package/dist/plugins/devices/index.d.ts.map +1 -0
  66. package/dist/plugins/devices/index.js +18 -0
  67. package/dist/plugins/devices/index.js.map +1 -0
  68. package/dist/plugins/devices/stores/index.d.ts +9 -0
  69. package/dist/plugins/devices/stores/index.d.ts.map +1 -0
  70. package/dist/plugins/devices/stores/index.js +9 -0
  71. package/dist/plugins/devices/stores/index.js.map +1 -0
  72. package/dist/plugins/devices/stores/postgres-store.d.ts +26 -0
  73. package/dist/plugins/devices/stores/postgres-store.d.ts.map +1 -0
  74. package/dist/plugins/devices/stores/postgres-store.js +199 -0
  75. package/dist/plugins/devices/stores/postgres-store.js.map +1 -0
  76. package/dist/plugins/devices/token-utils.d.ts +100 -0
  77. package/dist/plugins/devices/token-utils.d.ts.map +1 -0
  78. package/dist/plugins/devices/token-utils.js +162 -0
  79. package/dist/plugins/devices/token-utils.js.map +1 -0
  80. package/dist/plugins/devices/types.d.ts +307 -0
  81. package/dist/plugins/devices/types.d.ts.map +1 -0
  82. package/dist/plugins/devices/types.js +10 -0
  83. package/dist/plugins/devices/types.js.map +1 -0
  84. package/dist/plugins/index.d.ts +18 -4
  85. package/dist/plugins/index.d.ts.map +1 -1
  86. package/dist/plugins/index.js +16 -2
  87. package/dist/plugins/index.js.map +1 -1
  88. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts +5 -0
  89. package/dist/plugins/notifications/__tests__/notifications-manager.test.d.ts.map +1 -0
  90. package/dist/plugins/notifications/__tests__/notifications-manager.test.js +470 -0
  91. package/dist/plugins/notifications/__tests__/notifications-manager.test.js.map +1 -0
  92. package/dist/plugins/notifications/index.d.ts +71 -0
  93. package/dist/plugins/notifications/index.d.ts.map +1 -0
  94. package/dist/plugins/notifications/index.js +72 -0
  95. package/dist/plugins/notifications/index.js.map +1 -0
  96. package/dist/plugins/notifications/notifications-manager.d.ts +182 -0
  97. package/dist/plugins/notifications/notifications-manager.d.ts.map +1 -0
  98. package/dist/plugins/notifications/notifications-manager.js +610 -0
  99. package/dist/plugins/notifications/notifications-manager.js.map +1 -0
  100. package/dist/plugins/notifications/notifications-plugin.d.ts +83 -0
  101. package/dist/plugins/notifications/notifications-plugin.d.ts.map +1 -0
  102. package/dist/plugins/notifications/notifications-plugin.js +337 -0
  103. package/dist/plugins/notifications/notifications-plugin.js.map +1 -0
  104. package/dist/plugins/notifications/types.d.ts +164 -0
  105. package/dist/plugins/notifications/types.d.ts.map +1 -0
  106. package/dist/plugins/notifications/types.js +9 -0
  107. package/dist/plugins/notifications/types.js.map +1 -0
  108. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts +12 -0
  109. package/dist/plugins/parental/__tests__/parental-plugin.test.d.ts.map +1 -0
  110. package/dist/plugins/parental/__tests__/parental-plugin.test.js +349 -0
  111. package/dist/plugins/parental/__tests__/parental-plugin.test.js.map +1 -0
  112. package/dist/plugins/parental/adapters/index.d.ts +8 -0
  113. package/dist/plugins/parental/adapters/index.d.ts.map +1 -0
  114. package/dist/plugins/parental/adapters/index.js +7 -0
  115. package/dist/plugins/parental/adapters/index.js.map +1 -0
  116. package/dist/plugins/parental/adapters/kids-adapter.d.ts +24 -0
  117. package/dist/plugins/parental/adapters/kids-adapter.d.ts.map +1 -0
  118. package/dist/plugins/parental/adapters/kids-adapter.js +174 -0
  119. package/dist/plugins/parental/adapters/kids-adapter.js.map +1 -0
  120. package/dist/plugins/parental/index.d.ts +14 -0
  121. package/dist/plugins/parental/index.d.ts.map +1 -0
  122. package/dist/plugins/parental/index.js +15 -0
  123. package/dist/plugins/parental/index.js.map +1 -0
  124. package/dist/plugins/parental/parental-plugin.d.ts +88 -0
  125. package/dist/plugins/parental/parental-plugin.d.ts.map +1 -0
  126. package/dist/plugins/parental/parental-plugin.js +666 -0
  127. package/dist/plugins/parental/parental-plugin.js.map +1 -0
  128. package/dist/plugins/parental/stores/index.d.ts +7 -0
  129. package/dist/plugins/parental/stores/index.d.ts.map +1 -0
  130. package/dist/plugins/parental/stores/index.js +7 -0
  131. package/dist/plugins/parental/stores/index.js.map +1 -0
  132. package/dist/plugins/parental/stores/postgres-store.d.ts +10 -0
  133. package/dist/plugins/parental/stores/postgres-store.d.ts.map +1 -0
  134. package/dist/plugins/parental/stores/postgres-store.js +209 -0
  135. package/dist/plugins/parental/stores/postgres-store.js.map +1 -0
  136. package/dist/plugins/parental/types.d.ts +154 -0
  137. package/dist/plugins/parental/types.d.ts.map +1 -0
  138. package/dist/plugins/parental/types.js +10 -0
  139. package/dist/plugins/parental/types.js.map +1 -0
  140. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts +11 -0
  141. package/dist/plugins/profiles/__tests__/profiles-plugin.test.d.ts.map +1 -0
  142. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js +243 -0
  143. package/dist/plugins/profiles/__tests__/profiles-plugin.test.js.map +1 -0
  144. package/dist/plugins/profiles/index.d.ts +12 -0
  145. package/dist/plugins/profiles/index.d.ts.map +1 -0
  146. package/dist/plugins/profiles/index.js +13 -0
  147. package/dist/plugins/profiles/index.js.map +1 -0
  148. package/dist/plugins/profiles/profiles-plugin.d.ts +71 -0
  149. package/dist/plugins/profiles/profiles-plugin.d.ts.map +1 -0
  150. package/dist/plugins/profiles/profiles-plugin.js +481 -0
  151. package/dist/plugins/profiles/profiles-plugin.js.map +1 -0
  152. package/dist/plugins/profiles/stores/index.d.ts +9 -0
  153. package/dist/plugins/profiles/stores/index.d.ts.map +1 -0
  154. package/dist/plugins/profiles/stores/index.js +9 -0
  155. package/dist/plugins/profiles/stores/index.js.map +1 -0
  156. package/dist/plugins/profiles/stores/postgres-store.d.ts +18 -0
  157. package/dist/plugins/profiles/stores/postgres-store.d.ts.map +1 -0
  158. package/dist/plugins/profiles/stores/postgres-store.js +310 -0
  159. package/dist/plugins/profiles/stores/postgres-store.js.map +1 -0
  160. package/dist/plugins/profiles/types.d.ts +289 -0
  161. package/dist/plugins/profiles/types.d.ts.map +1 -0
  162. package/dist/plugins/profiles/types.js +10 -0
  163. package/dist/plugins/profiles/types.js.map +1 -0
  164. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
  165. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
  166. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
  167. package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
  168. package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
  169. package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
  170. package/dist/plugins/rate-limit/cleanup.js +72 -0
  171. package/dist/plugins/rate-limit/cleanup.js.map +1 -0
  172. package/dist/plugins/rate-limit/env-config.d.ts +91 -0
  173. package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
  174. package/dist/plugins/rate-limit/env-config.js +318 -0
  175. package/dist/plugins/rate-limit/env-config.js.map +1 -0
  176. package/dist/plugins/rate-limit/index.d.ts +76 -0
  177. package/dist/plugins/rate-limit/index.d.ts.map +1 -0
  178. package/dist/plugins/rate-limit/index.js +79 -0
  179. package/dist/plugins/rate-limit/index.js.map +1 -0
  180. package/dist/plugins/rate-limit/middleware.d.ts +40 -0
  181. package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
  182. package/dist/plugins/rate-limit/middleware.js +169 -0
  183. package/dist/plugins/rate-limit/middleware.js.map +1 -0
  184. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
  185. package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
  186. package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
  187. package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
  188. package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
  189. package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
  190. package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
  191. package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
  192. package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
  193. package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
  194. package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
  195. package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
  196. package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
  197. package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
  198. package/dist/plugins/rate-limit/stores/index.js +8 -0
  199. package/dist/plugins/rate-limit/stores/index.js.map +1 -0
  200. package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
  201. package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
  202. package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
  203. package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
  204. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
  205. package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
  206. package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
  207. package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
  208. package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
  209. package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
  210. package/dist/plugins/rate-limit/strategies/index.js +27 -0
  211. package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
  212. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
  213. package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
  214. package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
  215. package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
  216. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
  217. package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
  218. package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
  219. package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
  220. package/dist/plugins/rate-limit/types.d.ts +265 -0
  221. package/dist/plugins/rate-limit/types.d.ts.map +1 -0
  222. package/dist/plugins/rate-limit/types.js +9 -0
  223. package/dist/plugins/rate-limit/types.js.map +1 -0
  224. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts +11 -0
  225. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.d.ts.map +1 -0
  226. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js +305 -0
  227. package/dist/plugins/subscriptions/__tests__/subscriptions-plugin.test.js.map +1 -0
  228. package/dist/plugins/subscriptions/index.d.ts +12 -0
  229. package/dist/plugins/subscriptions/index.d.ts.map +1 -0
  230. package/dist/plugins/subscriptions/index.js +13 -0
  231. package/dist/plugins/subscriptions/index.js.map +1 -0
  232. package/dist/plugins/subscriptions/stores/index.d.ts +9 -0
  233. package/dist/plugins/subscriptions/stores/index.d.ts.map +1 -0
  234. package/dist/plugins/subscriptions/stores/index.js +9 -0
  235. package/dist/plugins/subscriptions/stores/index.js.map +1 -0
  236. package/dist/plugins/subscriptions/stores/postgres-store.d.ts +14 -0
  237. package/dist/plugins/subscriptions/stores/postgres-store.d.ts.map +1 -0
  238. package/dist/plugins/subscriptions/stores/postgres-store.js +359 -0
  239. package/dist/plugins/subscriptions/stores/postgres-store.js.map +1 -0
  240. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts +82 -0
  241. package/dist/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -0
  242. package/dist/plugins/subscriptions/subscriptions-plugin.js +449 -0
  243. package/dist/plugins/subscriptions/subscriptions-plugin.js.map +1 -0
  244. package/dist/plugins/subscriptions/types.d.ts +308 -0
  245. package/dist/plugins/subscriptions/types.d.ts.map +1 -0
  246. package/dist/plugins/subscriptions/types.js +10 -0
  247. package/dist/plugins/subscriptions/types.js.map +1 -0
  248. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts +11 -0
  249. package/dist/plugins/usage/__tests__/usage-plugin.test.d.ts.map +1 -0
  250. package/dist/plugins/usage/__tests__/usage-plugin.test.js +218 -0
  251. package/dist/plugins/usage/__tests__/usage-plugin.test.js.map +1 -0
  252. package/dist/plugins/usage/index.d.ts +12 -0
  253. package/dist/plugins/usage/index.d.ts.map +1 -0
  254. package/dist/plugins/usage/index.js +13 -0
  255. package/dist/plugins/usage/index.js.map +1 -0
  256. package/dist/plugins/usage/stores/index.d.ts +9 -0
  257. package/dist/plugins/usage/stores/index.d.ts.map +1 -0
  258. package/dist/plugins/usage/stores/index.js +9 -0
  259. package/dist/plugins/usage/stores/index.js.map +1 -0
  260. package/dist/plugins/usage/stores/postgres-store.d.ts +14 -0
  261. package/dist/plugins/usage/stores/postgres-store.d.ts.map +1 -0
  262. package/dist/plugins/usage/stores/postgres-store.js +146 -0
  263. package/dist/plugins/usage/stores/postgres-store.js.map +1 -0
  264. package/dist/plugins/usage/types.d.ts +195 -0
  265. package/dist/plugins/usage/types.d.ts.map +1 -0
  266. package/dist/plugins/usage/types.js +10 -0
  267. package/dist/plugins/usage/types.js.map +1 -0
  268. package/dist/plugins/usage/usage-plugin.d.ts +51 -0
  269. package/dist/plugins/usage/usage-plugin.d.ts.map +1 -0
  270. package/dist/plugins/usage/usage-plugin.js +412 -0
  271. package/dist/plugins/usage/usage-plugin.js.map +1 -0
  272. package/dist/plugins/users/__tests__/postgres-store.test.d.ts +10 -0
  273. package/dist/plugins/users/__tests__/postgres-store.test.d.ts.map +1 -0
  274. package/dist/plugins/users/__tests__/postgres-store.test.js +229 -0
  275. package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -0
  276. package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
  277. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
  278. package/dist/plugins/users/index.d.ts +2 -2
  279. package/dist/plugins/users/index.d.ts.map +1 -1
  280. package/dist/plugins/users/index.js +1 -1
  281. package/dist/plugins/users/index.js.map +1 -1
  282. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
  283. package/dist/plugins/users/stores/postgres-store.js +76 -0
  284. package/dist/plugins/users/stores/postgres-store.js.map +1 -1
  285. package/dist/plugins/users/types.d.ts +74 -6
  286. package/dist/plugins/users/types.d.ts.map +1 -1
  287. package/dist/plugins/users/users-plugin.d.ts +15 -1
  288. package/dist/plugins/users/users-plugin.d.ts.map +1 -1
  289. package/dist/plugins/users/users-plugin.js +29 -0
  290. package/dist/plugins/users/users-plugin.js.map +1 -1
  291. package/dist-ui/assets/index-CynOqPkb.js +469 -0
  292. package/dist-ui/assets/{index-BY8OxNgO.js.map → index-CynOqPkb.js.map} +1 -1
  293. package/dist-ui/index.html +1 -1
  294. package/dist-ui-lib/api/controlPanelApi.d.ts +187 -0
  295. package/dist-ui-lib/components/StatCard.d.ts +16 -0
  296. package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
  297. package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
  298. package/dist-ui-lib/dashboard/widgets/NotificationsStatsWidget.d.ts +12 -0
  299. package/dist-ui-lib/dashboard/widgets/index.d.ts +3 -0
  300. package/dist-ui-lib/index.js +3579 -2379
  301. package/dist-ui-lib/index.js.map +1 -1
  302. package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
  303. package/dist-ui-lib/pages/NotificationsPage.d.ts +9 -0
  304. package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
  305. package/dist-ui-lib/utils/formatters.d.ts +19 -0
  306. package/package.json +1 -1
  307. package/src/core/control-panel.ts +128 -0
  308. package/src/core/types.ts +17 -0
  309. package/src/index.ts +216 -0
  310. package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
  311. package/src/plugins/auth/adapter-wrapper.ts +205 -0
  312. package/src/plugins/auth/config-store.test.ts +417 -0
  313. package/src/plugins/auth/config-store.ts +305 -0
  314. package/src/plugins/auth/env-config.ts +714 -7
  315. package/src/plugins/auth/index.ts +22 -1
  316. package/src/plugins/auth/types.ts +138 -0
  317. package/src/plugins/bans/bans-plugin.ts +15 -3
  318. package/src/plugins/devices/__tests__/devices-plugin.test.ts +551 -0
  319. package/src/plugins/devices/__tests__/token-utils.test.ts +264 -0
  320. package/src/plugins/devices/adapters/compute-adapter.ts +139 -0
  321. package/src/plugins/devices/adapters/index.ts +13 -0
  322. package/src/plugins/devices/adapters/mobile-adapter.ts +179 -0
  323. package/src/plugins/devices/devices-plugin.ts +538 -0
  324. package/src/plugins/devices/index.ts +69 -0
  325. package/src/plugins/devices/stores/index.ts +9 -0
  326. package/src/plugins/devices/stores/postgres-store.ts +304 -0
  327. package/src/plugins/devices/token-utils.ts +213 -0
  328. package/src/plugins/devices/types.ts +351 -0
  329. package/src/plugins/index.ts +267 -0
  330. package/src/plugins/notifications/__tests__/notifications-manager.test.ts +637 -0
  331. package/src/plugins/notifications/index.ts +91 -0
  332. package/src/plugins/notifications/notifications-manager.ts +773 -0
  333. package/src/plugins/notifications/notifications-plugin.ts +398 -0
  334. package/src/plugins/notifications/types.ts +207 -0
  335. package/src/plugins/parental/__tests__/parental-plugin.test.ts +465 -0
  336. package/src/plugins/parental/adapters/index.ts +8 -0
  337. package/src/plugins/parental/adapters/kids-adapter.ts +206 -0
  338. package/src/plugins/parental/index.ts +55 -0
  339. package/src/plugins/parental/parental-plugin.ts +759 -0
  340. package/src/plugins/parental/stores/index.ts +7 -0
  341. package/src/plugins/parental/stores/postgres-store.ts +304 -0
  342. package/src/plugins/parental/types.ts +180 -0
  343. package/src/plugins/profiles/__tests__/profiles-plugin.test.ts +321 -0
  344. package/src/plugins/profiles/index.ts +49 -0
  345. package/src/plugins/profiles/profiles-plugin.ts +546 -0
  346. package/src/plugins/profiles/stores/index.ts +9 -0
  347. package/src/plugins/profiles/stores/postgres-store.ts +439 -0
  348. package/src/plugins/profiles/types.ts +338 -0
  349. package/src/plugins/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
  350. package/src/plugins/rate-limit/cleanup.ts +117 -0
  351. package/src/plugins/rate-limit/env-config.ts +400 -0
  352. package/src/plugins/rate-limit/index.ts +128 -0
  353. package/src/plugins/rate-limit/middleware.ts +212 -0
  354. package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
  355. package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
  356. package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
  357. package/src/plugins/rate-limit/stores/index.ts +8 -0
  358. package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
  359. package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
  360. package/src/plugins/rate-limit/strategies/index.ts +30 -0
  361. package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
  362. package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
  363. package/src/plugins/rate-limit/types.ts +338 -0
  364. package/src/plugins/subscriptions/__tests__/subscriptions-plugin.test.ts +404 -0
  365. package/src/plugins/subscriptions/index.ts +51 -0
  366. package/src/plugins/subscriptions/stores/index.ts +9 -0
  367. package/src/plugins/subscriptions/stores/postgres-store.ts +482 -0
  368. package/src/plugins/subscriptions/subscriptions-plugin.ts +530 -0
  369. package/src/plugins/subscriptions/types.ts +355 -0
  370. package/src/plugins/usage/__tests__/usage-plugin.test.ts +288 -0
  371. package/src/plugins/usage/index.ts +39 -0
  372. package/src/plugins/usage/stores/index.ts +9 -0
  373. package/src/plugins/usage/stores/postgres-store.ts +213 -0
  374. package/src/plugins/usage/types.ts +222 -0
  375. package/src/plugins/usage/usage-plugin.ts +484 -0
  376. package/src/plugins/users/__tests__/postgres-store.test.ts +326 -0
  377. package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
  378. package/src/plugins/users/index.ts +6 -0
  379. package/src/plugins/users/stores/postgres-store.ts +104 -0
  380. package/src/plugins/users/types.ts +82 -6
  381. package/src/plugins/users/users-plugin.ts +37 -0
  382. package/ui/src/App.tsx +36 -14
  383. package/ui/src/api/controlPanelApi.ts +329 -6
  384. package/ui/src/components/StatCard.tsx +58 -0
  385. package/ui/src/dashboard/builtInWidgets.tsx +7 -1
  386. package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
  387. package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
  388. package/ui/src/dashboard/widgets/NotificationsStatsWidget.tsx +167 -0
  389. package/ui/src/dashboard/widgets/index.ts +3 -0
  390. package/ui/src/pages/AuthPage.tsx +986 -142
  391. package/ui/src/pages/IntegrationsPage.tsx +288 -0
  392. package/ui/src/pages/NotificationsPage.tsx +417 -0
  393. package/ui/src/pages/RateLimitPage.tsx +292 -0
  394. package/ui/src/utils/formatters.ts +33 -0
  395. package/dist-ui/assets/index-BY8OxNgO.js +0 -465
@@ -0,0 +1,402 @@
1
+ /**
2
+ * PostgreSQL Rate Limit Store
3
+ *
4
+ * Rate limit storage implementation using PostgreSQL with Row-Level Security (RLS).
5
+ * Follows the same pattern as the preferences plugin's postgres-store.
6
+ *
7
+ * RLS Context Pattern:
8
+ * Each operation uses an explicit transaction and sets `app.current_user_id`
9
+ * as a transaction-local configuration variable. The RLS policy checks this
10
+ * variable to enforce that users can only access their own rate limits.
11
+ *
12
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
13
+ */
14
+
15
+ import type {
16
+ RateLimitStore,
17
+ PostgresRateLimitStoreConfig,
18
+ StoredLimit,
19
+ IncrementOptions,
20
+ } from '../types.js';
21
+
22
+ // Pool interface (from pg package)
23
+ interface PgPool {
24
+ query(text: string, values?: unknown[]): Promise<{ rows: unknown[]; rowCount: number | null }>;
25
+ connect(): Promise<PgPoolClient>;
26
+ }
27
+
28
+ interface PgPoolClient {
29
+ query(text: string, values?: unknown[]): Promise<{ rows: unknown[]; rowCount: number | null }>;
30
+ release(): void;
31
+ }
32
+
33
+ /**
34
+ * Execute a function within an RLS-protected transaction
35
+ *
36
+ * This helper ensures that:
37
+ * 1. All queries run within the same transaction
38
+ * 2. The RLS context is set before any data access
39
+ * 3. The transaction is properly committed or rolled back
40
+ *
41
+ * @param pool PostgreSQL pool
42
+ * @param userId User ID to set as the RLS context (optional for IP-only limits)
43
+ * @param callback Function to execute within the transaction
44
+ */
45
+ async function withRLSContext<T>(
46
+ pool: PgPool,
47
+ userId: string | undefined,
48
+ callback: (client: PgPoolClient) => Promise<T>
49
+ ): Promise<T> {
50
+ const client = await pool.connect();
51
+ try {
52
+ await client.query('BEGIN');
53
+ // Set transaction-local user context for RLS
54
+ // If no userId, set to empty string (allows IP-only limits via RLS policy)
55
+ await client.query(
56
+ "SELECT set_config('app.current_user_id', $1, true)",
57
+ [userId || '']
58
+ );
59
+ const result = await callback(client);
60
+ await client.query('COMMIT');
61
+ return result;
62
+ } catch (error) {
63
+ await client.query('ROLLBACK');
64
+ throw error;
65
+ } finally {
66
+ client.release();
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Create a PostgreSQL rate limit store with RLS
72
+ *
73
+ * @param config Configuration including a pg Pool instance
74
+ * @returns RateLimitStore implementation
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * import { Pool } from 'pg';
79
+ * import { postgresRateLimitStore } from '@qwickapps/server';
80
+ *
81
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
82
+ * const store = postgresRateLimitStore({ pool });
83
+ *
84
+ * // Or with lazy initialization:
85
+ * const store = postgresRateLimitStore({ pool: () => getPostgres().getPool() });
86
+ * ```
87
+ */
88
+ export function postgresRateLimitStore(config: PostgresRateLimitStoreConfig): RateLimitStore {
89
+ const {
90
+ pool: poolOrFn,
91
+ tableName = 'rate_limits',
92
+ schema = 'public',
93
+ autoCreateTables = true,
94
+ enableRLS = true,
95
+ } = config;
96
+
97
+ // Helper to get pool (supports lazy initialization via function)
98
+ const getPool = (): PgPool => {
99
+ const pool = typeof poolOrFn === 'function' ? poolOrFn() : poolOrFn;
100
+ if (!pool || typeof (pool as PgPool).query !== 'function') {
101
+ throw new Error('Invalid pool: must have query method');
102
+ }
103
+ return pool as PgPool;
104
+ };
105
+
106
+ const tableFullName = `"${schema}"."${tableName}"`;
107
+
108
+ return {
109
+ name: 'postgres',
110
+
111
+ async initialize(): Promise<void> {
112
+ if (!autoCreateTables) return;
113
+
114
+ const pool = getPool();
115
+
116
+ // Create table
117
+ await pool.query(`
118
+ CREATE TABLE IF NOT EXISTS ${tableFullName} (
119
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
120
+
121
+ -- Scope identifiers (nullable, use what applies)
122
+ user_id UUID,
123
+ tenant_id UUID,
124
+ ip_address INET,
125
+
126
+ -- Limit key (composite identifier)
127
+ limit_key VARCHAR(500) NOT NULL,
128
+
129
+ -- Strategy and configuration
130
+ strategy VARCHAR(50) NOT NULL DEFAULT 'sliding-window',
131
+ max_requests INTEGER NOT NULL,
132
+ window_ms INTEGER NOT NULL,
133
+
134
+ -- Current state
135
+ current_count INTEGER DEFAULT 0,
136
+ window_start TIMESTAMPTZ NOT NULL,
137
+ window_end TIMESTAMPTZ NOT NULL,
138
+
139
+ -- Token bucket specific (nullable)
140
+ tokens_remaining NUMERIC,
141
+ last_refill TIMESTAMPTZ,
142
+
143
+ -- Metadata
144
+ metadata JSONB,
145
+ created_at TIMESTAMPTZ DEFAULT NOW(),
146
+ updated_at TIMESTAMPTZ DEFAULT NOW()
147
+ );
148
+ `);
149
+
150
+ // Create indexes
151
+ await pool.query(`
152
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_key ON ${tableFullName}(limit_key);
153
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_key_window ON ${tableFullName}(limit_key, window_start);
154
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_user ON ${tableFullName}(user_id) WHERE user_id IS NOT NULL;
155
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_tenant ON ${tableFullName}(tenant_id) WHERE tenant_id IS NOT NULL;
156
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_cleanup ON ${tableFullName}(window_end);
157
+ `);
158
+
159
+ // Enable RLS if configured
160
+ if (enableRLS) {
161
+ await pool.query(`
162
+ ALTER TABLE ${tableFullName} ENABLE ROW LEVEL SECURITY;
163
+ ALTER TABLE ${tableFullName} FORCE ROW LEVEL SECURITY;
164
+ `);
165
+
166
+ // Create or replace the RLS policy
167
+ // Drop existing policy first to avoid errors on re-initialization
168
+ await pool.query(`
169
+ DROP POLICY IF EXISTS "${tableName}_access" ON ${tableFullName};
170
+ `);
171
+
172
+ // RLS policy: Users can access their own limits OR IP-only limits (no user)
173
+ await pool.query(`
174
+ CREATE POLICY "${tableName}_access" ON ${tableFullName}
175
+ FOR ALL
176
+ USING (
177
+ user_id::text = current_setting('app.current_user_id', true)
178
+ OR (user_id IS NULL AND current_setting('app.current_user_id', true) = '')
179
+ )
180
+ WITH CHECK (
181
+ user_id::text = current_setting('app.current_user_id', true)
182
+ OR (user_id IS NULL AND current_setting('app.current_user_id', true) = '')
183
+ );
184
+ `);
185
+ }
186
+ },
187
+
188
+ async get(key: string, userId?: string): Promise<StoredLimit | null> {
189
+ return withRLSContext(getPool(), userId, async (client) => {
190
+ const result = await client.query(
191
+ `SELECT * FROM ${tableFullName}
192
+ WHERE limit_key = $1
193
+ AND window_end > NOW()
194
+ ORDER BY window_start DESC
195
+ LIMIT 1`,
196
+ [key]
197
+ );
198
+
199
+ if (result.rows.length === 0) {
200
+ return null;
201
+ }
202
+
203
+ const row = result.rows[0] as Record<string, unknown>;
204
+ return {
205
+ id: row.id as string,
206
+ key: row.limit_key as string,
207
+ count: row.current_count as number,
208
+ maxRequests: row.max_requests as number,
209
+ windowMs: row.window_ms as number,
210
+ windowStart: new Date(row.window_start as string),
211
+ windowEnd: new Date(row.window_end as string),
212
+ strategy: row.strategy as StoredLimit['strategy'],
213
+ userId: row.user_id as string | undefined,
214
+ tenantId: row.tenant_id as string | undefined,
215
+ ipAddress: row.ip_address as string | undefined,
216
+ tokensRemaining: row.tokens_remaining as number | undefined,
217
+ lastRefill: row.last_refill ? new Date(row.last_refill as string) : undefined,
218
+ createdAt: new Date(row.created_at as string),
219
+ updatedAt: new Date(row.updated_at as string),
220
+ };
221
+ });
222
+ },
223
+
224
+ async increment(key: string, options: IncrementOptions): Promise<StoredLimit> {
225
+ const { maxRequests, windowMs, strategy, userId, tenantId, ipAddress, amount = 1 } = options;
226
+
227
+ return withRLSContext(getPool(), userId, async (client) => {
228
+ const now = new Date();
229
+ const windowStart = new Date(now.getTime() - (now.getTime() % windowMs));
230
+ const windowEnd = new Date(windowStart.getTime() + windowMs);
231
+
232
+ // For token bucket, handle differently
233
+ if (strategy === 'token-bucket') {
234
+ // Get existing record or create new one
235
+ const existing = await client.query(
236
+ `SELECT * FROM ${tableFullName}
237
+ WHERE limit_key = $1
238
+ ORDER BY created_at DESC
239
+ LIMIT 1`,
240
+ [key]
241
+ );
242
+
243
+ if (existing.rows.length > 0) {
244
+ const row = existing.rows[0] as Record<string, unknown>;
245
+ const lastRefill = row.last_refill ? new Date(row.last_refill as string).getTime() : now.getTime();
246
+ const tokensRemaining = row.tokens_remaining as number ?? maxRequests;
247
+
248
+ // Calculate refill
249
+ const elapsed = now.getTime() - lastRefill;
250
+ const refillRate = maxRequests / windowMs;
251
+ const newTokens = Math.min(maxRequests, tokensRemaining + elapsed * refillRate - 1);
252
+
253
+ await client.query(
254
+ `UPDATE ${tableFullName}
255
+ SET tokens_remaining = $1,
256
+ last_refill = $2,
257
+ current_count = $3,
258
+ updated_at = NOW()
259
+ WHERE id = $4`,
260
+ [newTokens, now, Math.floor(maxRequests - newTokens), row.id]
261
+ );
262
+
263
+ return {
264
+ id: row.id as string,
265
+ key,
266
+ count: Math.floor(maxRequests - newTokens),
267
+ maxRequests,
268
+ windowMs,
269
+ windowStart: new Date(row.window_start as string),
270
+ windowEnd: new Date(row.window_end as string),
271
+ strategy,
272
+ userId,
273
+ tenantId,
274
+ ipAddress,
275
+ tokensRemaining: newTokens,
276
+ lastRefill: now,
277
+ createdAt: new Date(row.created_at as string),
278
+ updatedAt: now,
279
+ };
280
+ }
281
+
282
+ // Create new record with full bucket minus 1
283
+ const newTokens = maxRequests - 1;
284
+ const insertResult = await client.query(
285
+ `INSERT INTO ${tableFullName}
286
+ (limit_key, strategy, max_requests, window_ms, current_count,
287
+ window_start, window_end, user_id, tenant_id, ip_address,
288
+ tokens_remaining, last_refill)
289
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
290
+ RETURNING *`,
291
+ [key, strategy, maxRequests, windowMs, 1, windowStart, windowEnd,
292
+ userId || null, tenantId || null, ipAddress || null, newTokens, now]
293
+ );
294
+
295
+ const row = insertResult.rows[0] as Record<string, unknown>;
296
+ return {
297
+ id: row.id as string,
298
+ key,
299
+ count: 1,
300
+ maxRequests,
301
+ windowMs,
302
+ windowStart,
303
+ windowEnd,
304
+ strategy,
305
+ userId,
306
+ tenantId,
307
+ ipAddress,
308
+ tokensRemaining: newTokens,
309
+ lastRefill: now,
310
+ createdAt: now,
311
+ updatedAt: now,
312
+ };
313
+ }
314
+
315
+ // For sliding-window and fixed-window, use UPDATE-then-INSERT pattern
316
+ // This avoids needing a unique constraint on (limit_key, window_start)
317
+
318
+ // First try to update existing record in current window
319
+ const updateResult = await client.query(
320
+ `UPDATE ${tableFullName}
321
+ SET current_count = current_count + $1,
322
+ updated_at = NOW()
323
+ WHERE limit_key = $2
324
+ AND window_start = $3
325
+ RETURNING *`,
326
+ [amount, key, windowStart]
327
+ );
328
+
329
+ if (updateResult.rows.length > 0) {
330
+ const row = updateResult.rows[0] as Record<string, unknown>;
331
+ return {
332
+ id: row.id as string,
333
+ key,
334
+ count: row.current_count as number,
335
+ maxRequests,
336
+ windowMs,
337
+ windowStart,
338
+ windowEnd,
339
+ strategy,
340
+ userId,
341
+ tenantId,
342
+ ipAddress,
343
+ createdAt: new Date(row.created_at as string),
344
+ updatedAt: now,
345
+ };
346
+ }
347
+
348
+ // No existing record - insert new
349
+ const insertResult = await client.query(
350
+ `INSERT INTO ${tableFullName}
351
+ (limit_key, strategy, max_requests, window_ms, current_count,
352
+ window_start, window_end, user_id, tenant_id, ip_address)
353
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
354
+ RETURNING *`,
355
+ [key, strategy, maxRequests, windowMs, amount, windowStart, windowEnd,
356
+ userId || null, tenantId || null, ipAddress || null]
357
+ );
358
+
359
+ const row = insertResult.rows[0] as Record<string, unknown>;
360
+ return {
361
+ id: row.id as string,
362
+ key,
363
+ count: amount,
364
+ maxRequests,
365
+ windowMs,
366
+ windowStart,
367
+ windowEnd,
368
+ strategy,
369
+ userId,
370
+ tenantId,
371
+ ipAddress,
372
+ createdAt: now,
373
+ updatedAt: now,
374
+ };
375
+ });
376
+ },
377
+
378
+ async clear(key: string, userId?: string): Promise<boolean> {
379
+ return withRLSContext(getPool(), userId, async (client) => {
380
+ const result = await client.query(
381
+ `DELETE FROM ${tableFullName} WHERE limit_key = $1`,
382
+ [key]
383
+ );
384
+ return (result.rowCount ?? 0) > 0;
385
+ });
386
+ },
387
+
388
+ async cleanup(): Promise<number> {
389
+ const pool = getPool();
390
+ // Cleanup runs without RLS context as it's a system operation
391
+ // We use a direct query without user context
392
+ const result = await pool.query(
393
+ `DELETE FROM ${tableFullName} WHERE window_end < NOW()`
394
+ );
395
+ return result.rowCount ?? 0;
396
+ },
397
+
398
+ async shutdown(): Promise<void> {
399
+ // Pool is managed externally, nothing to do here
400
+ },
401
+ };
402
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Fixed Window Rate Limit Strategy
3
+ *
4
+ * Implements a simple fixed window algorithm where requests are counted
5
+ * within discrete time windows. When a new window starts, the count resets.
6
+ *
7
+ * Pros:
8
+ * - Simple to implement and understand
9
+ * - Low memory overhead
10
+ *
11
+ * Cons:
12
+ * - Burst at boundary: allows 2x requests if timed at window edge
13
+ *
14
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
15
+ */
16
+
17
+ import type {
18
+ Strategy,
19
+ StrategyOptions,
20
+ StrategyContext,
21
+ LimitStatus,
22
+ CachedLimit,
23
+ } from '../types.js';
24
+
25
+ /**
26
+ * Create the fixed window strategy
27
+ */
28
+ export function createFixedWindowStrategy(): Strategy {
29
+ return {
30
+ name: 'fixed-window',
31
+
32
+ async check(
33
+ key: string,
34
+ options: StrategyOptions,
35
+ context: StrategyContext
36
+ ): Promise<LimitStatus> {
37
+ const { maxRequests, windowMs, increment = true } = options;
38
+ const { store, cache, userId, tenantId, ipAddress } = context;
39
+
40
+ const now = Date.now();
41
+ const windowStart = now - (now % windowMs); // Align to window boundary
42
+ const windowEnd = windowStart + windowMs;
43
+
44
+ // Try cache first
45
+ let cached = await cache.get(key);
46
+ let currentCount = 0;
47
+
48
+ if (cached && cached.windowStart === windowStart) {
49
+ // Cache hit and same window
50
+ currentCount = cached.count;
51
+ } else if (cached && cached.windowEnd <= now) {
52
+ // Window expired - reset count
53
+ currentCount = 0;
54
+ } else {
55
+ // Cache miss - check store
56
+ const stored = await store.get(key, userId);
57
+ if (stored) {
58
+ const storedWindowStart = stored.windowStart.getTime();
59
+ if (storedWindowStart === windowStart) {
60
+ currentCount = stored.count;
61
+ }
62
+ // If different window, count is 0 (new window)
63
+ }
64
+ }
65
+
66
+ // Check if limited
67
+ const limited = currentCount >= maxRequests;
68
+ const remaining = Math.max(0, maxRequests - currentCount - (increment && !limited ? 1 : 0));
69
+ const resetAt = Math.ceil(windowEnd / 1000);
70
+ const retryAfter = Math.max(0, Math.ceil((windowEnd - now) / 1000));
71
+
72
+ // Increment if requested and not limited
73
+ if (increment && !limited) {
74
+ // Increment in store
75
+ const updated = await store.increment(key, {
76
+ maxRequests,
77
+ windowMs,
78
+ strategy: 'fixed-window',
79
+ userId,
80
+ tenantId,
81
+ ipAddress,
82
+ amount: 1,
83
+ });
84
+
85
+ // Update cache
86
+ const newCached: CachedLimit = {
87
+ count: updated.count,
88
+ maxRequests,
89
+ windowStart,
90
+ windowEnd,
91
+ strategy: 'fixed-window',
92
+ };
93
+ await cache.set(key, newCached, windowMs);
94
+
95
+ return {
96
+ limited: false,
97
+ current: updated.count,
98
+ limit: maxRequests,
99
+ remaining: Math.max(0, maxRequests - updated.count),
100
+ resetAt,
101
+ retryAfter,
102
+ };
103
+ }
104
+
105
+ // Return status without incrementing
106
+ return {
107
+ limited,
108
+ current: currentCount,
109
+ limit: maxRequests,
110
+ remaining,
111
+ resetAt,
112
+ retryAfter,
113
+ };
114
+ },
115
+ };
116
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Rate Limit Strategies
3
+ *
4
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
5
+ */
6
+
7
+ export { createSlidingWindowStrategy } from './sliding-window.js';
8
+ export { createFixedWindowStrategy } from './fixed-window.js';
9
+ export { createTokenBucketStrategy } from './token-bucket.js';
10
+
11
+ import type { Strategy, RateLimitStrategy } from '../types.js';
12
+ import { createSlidingWindowStrategy } from './sliding-window.js';
13
+ import { createFixedWindowStrategy } from './fixed-window.js';
14
+ import { createTokenBucketStrategy } from './token-bucket.js';
15
+
16
+ /**
17
+ * Get a strategy by name
18
+ */
19
+ export function getStrategy(name: RateLimitStrategy): Strategy {
20
+ switch (name) {
21
+ case 'sliding-window':
22
+ return createSlidingWindowStrategy();
23
+ case 'fixed-window':
24
+ return createFixedWindowStrategy();
25
+ case 'token-bucket':
26
+ return createTokenBucketStrategy();
27
+ default:
28
+ throw new Error(`Unknown rate limit strategy: ${name}`);
29
+ }
30
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Sliding Window Rate Limit Strategy
3
+ *
4
+ * Implements a sliding window algorithm that provides smooth rate limiting
5
+ * without the "burst at boundary" problem of fixed windows.
6
+ *
7
+ * The algorithm works by:
8
+ * 1. Tracking request timestamps within a sliding window
9
+ * 2. On each request, counting requests in the current window
10
+ * 3. Old requests "slide out" as time passes
11
+ *
12
+ * For performance, we approximate using weighted window counting:
13
+ * - current_window_count + (previous_window_count * overlap_percentage)
14
+ *
15
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
16
+ */
17
+
18
+ import type {
19
+ Strategy,
20
+ StrategyOptions,
21
+ StrategyContext,
22
+ LimitStatus,
23
+ CachedLimit,
24
+ } from '../types.js';
25
+
26
+ /**
27
+ * Calculate the sliding window count using weighted approximation
28
+ */
29
+ function calculateSlidingCount(
30
+ currentCount: number,
31
+ previousCount: number,
32
+ windowMs: number,
33
+ windowStart: number
34
+ ): number {
35
+ const now = Date.now();
36
+ const elapsed = now - windowStart;
37
+ const overlap = Math.max(0, windowMs - elapsed) / windowMs;
38
+
39
+ // Weighted count: all of current window + portion of previous that overlaps
40
+ return Math.floor(currentCount + previousCount * overlap);
41
+ }
42
+
43
+ /**
44
+ * Create the sliding window strategy
45
+ */
46
+ export function createSlidingWindowStrategy(): Strategy {
47
+ return {
48
+ name: 'sliding-window',
49
+
50
+ async check(
51
+ key: string,
52
+ options: StrategyOptions,
53
+ context: StrategyContext
54
+ ): Promise<LimitStatus> {
55
+ const { maxRequests, windowMs, increment = true } = options;
56
+ const { store, cache, userId, tenantId, ipAddress } = context;
57
+
58
+ const now = Date.now();
59
+ const windowStart = now - (now % windowMs); // Align to window boundary
60
+ const windowEnd = windowStart + windowMs;
61
+
62
+ // Try cache first
63
+ let cached = await cache.get(key);
64
+ let currentCount = 0;
65
+ let previousCount = 0;
66
+
67
+ if (cached && cached.windowEnd > now) {
68
+ // Cache hit and window is still valid
69
+ currentCount = cached.count;
70
+
71
+ // For sliding window, we also need previous window's count
72
+ // This is stored in the cache with a special key
73
+ const prevCached = await cache.get(`${key}:prev`);
74
+ if (prevCached) {
75
+ previousCount = prevCached.count;
76
+ }
77
+ } else {
78
+ // Cache miss or expired - check store
79
+ const stored = await store.get(key, userId);
80
+ if (stored && stored.windowEnd.getTime() > now) {
81
+ currentCount = stored.count;
82
+ }
83
+ }
84
+
85
+ // Calculate sliding window count
86
+ const slidingCount = calculateSlidingCount(
87
+ currentCount,
88
+ previousCount,
89
+ windowMs,
90
+ windowStart
91
+ );
92
+
93
+ // Check if limited
94
+ const limited = slidingCount >= maxRequests;
95
+ const remaining = Math.max(0, maxRequests - slidingCount - (increment && !limited ? 1 : 0));
96
+ const resetAt = Math.ceil(windowEnd / 1000);
97
+ const retryAfter = Math.max(0, Math.ceil((windowEnd - now) / 1000));
98
+
99
+ // Increment if requested and not limited
100
+ if (increment && !limited) {
101
+ // Check if we're in a new window - if so, save old count as previous
102
+ if (cached && cached.windowStart !== windowStart && cached.windowStart > 0) {
103
+ // Window rolled over - save old window's data as previous
104
+ const prevCached: CachedLimit = {
105
+ count: cached.count,
106
+ maxRequests,
107
+ windowStart: cached.windowStart,
108
+ windowEnd: cached.windowEnd,
109
+ strategy: 'sliding-window',
110
+ };
111
+ // Save previous window for overlap calculation (keep for 2x window duration)
112
+ await cache.set(`${key}:prev`, prevCached, windowMs * 2);
113
+ }
114
+
115
+ // Increment in store
116
+ const updated = await store.increment(key, {
117
+ maxRequests,
118
+ windowMs,
119
+ strategy: 'sliding-window',
120
+ userId,
121
+ tenantId,
122
+ ipAddress,
123
+ amount: 1,
124
+ });
125
+
126
+ // Update cache with current window
127
+ const newCached: CachedLimit = {
128
+ count: updated.count,
129
+ maxRequests,
130
+ windowStart,
131
+ windowEnd,
132
+ strategy: 'sliding-window',
133
+ };
134
+ await cache.set(key, newCached, windowMs);
135
+
136
+ return {
137
+ limited: false,
138
+ current: updated.count,
139
+ limit: maxRequests,
140
+ remaining: Math.max(0, maxRequests - updated.count),
141
+ resetAt,
142
+ retryAfter,
143
+ };
144
+ }
145
+
146
+ // Return status without incrementing
147
+ return {
148
+ limited,
149
+ current: slidingCount,
150
+ limit: maxRequests,
151
+ remaining,
152
+ resetAt,
153
+ retryAfter,
154
+ };
155
+ },
156
+ };
157
+ }