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