convex-cms 0.0.2 → 0.0.3

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 (265) hide show
  1. package/admin-dist/nitro.json +15 -0
  2. package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
  3. package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
  4. package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
  5. package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
  6. package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
  7. package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
  8. package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
  9. package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
  10. package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
  11. package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
  12. package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
  13. package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
  14. package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
  15. package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
  16. package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
  17. package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
  18. package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
  19. package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
  20. package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
  21. package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
  22. package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
  23. package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
  24. package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
  25. package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
  26. package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
  27. package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
  28. package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
  29. package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
  30. package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
  31. package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
  32. package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
  33. package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
  34. package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
  35. package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
  36. package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
  37. package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
  38. package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
  39. package/admin-dist/public/favicon.ico +0 -0
  40. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
  41. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
  42. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
  43. package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
  44. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
  45. package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
  46. package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
  47. package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
  48. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
  49. package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
  50. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
  51. package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
  52. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
  53. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
  54. package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
  55. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
  56. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
  57. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
  58. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
  59. package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
  60. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
  61. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
  62. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
  63. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
  64. package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
  65. package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
  66. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
  67. package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
  68. package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
  69. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
  70. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
  71. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
  72. package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
  73. package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
  83. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
  84. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
  85. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
  86. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
  87. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
  88. package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
  89. package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
  90. package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
  91. package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
  92. package/admin-dist/server/_libs/clsx.mjs +16 -0
  93. package/admin-dist/server/_libs/cmdk.mjs +315 -0
  94. package/admin-dist/server/_libs/convex.mjs +4841 -0
  95. package/admin-dist/server/_libs/cookie-es.mjs +58 -0
  96. package/admin-dist/server/_libs/croner.mjs +1 -0
  97. package/admin-dist/server/_libs/crossws.mjs +1 -0
  98. package/admin-dist/server/_libs/date-fns.mjs +1716 -0
  99. package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
  100. package/admin-dist/server/_libs/get-nonce.mjs +9 -0
  101. package/admin-dist/server/_libs/h3-v2.mjs +277 -0
  102. package/admin-dist/server/_libs/h3.mjs +401 -0
  103. package/admin-dist/server/_libs/hookable.mjs +1 -0
  104. package/admin-dist/server/_libs/isbot.mjs +20 -0
  105. package/admin-dist/server/_libs/lucide-react.mjs +850 -0
  106. package/admin-dist/server/_libs/ohash.mjs +1 -0
  107. package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
  108. package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
  109. package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
  110. package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
  111. package/admin-dist/server/_libs/rou3.mjs +8 -0
  112. package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
  113. package/admin-dist/server/_libs/seroval.mjs +1765 -0
  114. package/admin-dist/server/_libs/srvx.mjs +719 -0
  115. package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
  116. package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
  117. package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
  118. package/admin-dist/server/_libs/tslib.mjs +39 -0
  119. package/admin-dist/server/_libs/ufo.mjs +54 -0
  120. package/admin-dist/server/_libs/unctx.mjs +1 -0
  121. package/admin-dist/server/_libs/unstorage.mjs +1 -0
  122. package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
  123. package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
  124. package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
  125. package/admin-dist/server/_libs/zod.mjs +4223 -0
  126. package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
  127. package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
  128. package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
  129. package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
  130. package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
  131. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
  132. package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
  133. package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
  134. package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
  135. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
  136. package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
  137. package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
  138. package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
  139. package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
  140. package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
  141. package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
  142. package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
  143. package/admin-dist/server/_ssr/index.mjs +1275 -0
  144. package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
  145. package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
  146. package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
  147. package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
  148. package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
  149. package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
  150. package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
  151. package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  152. package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
  153. package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
  154. package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
  155. package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
  156. package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
  157. package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
  158. package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
  159. package/admin-dist/server/favicon.ico +0 -0
  160. package/admin-dist/server/index.mjs +627 -0
  161. package/dist/cli/index.js +0 -0
  162. package/dist/client/admin-config.d.ts +0 -1
  163. package/dist/client/admin-config.d.ts.map +1 -1
  164. package/dist/client/admin-config.js +0 -1
  165. package/dist/client/admin-config.js.map +1 -1
  166. package/dist/client/adminApi.d.ts.map +1 -1
  167. package/dist/client/agentTools.d.ts +1237 -135
  168. package/dist/client/agentTools.d.ts.map +1 -1
  169. package/dist/client/agentTools.js +33 -9
  170. package/dist/client/agentTools.js.map +1 -1
  171. package/dist/client/index.d.ts +1 -1
  172. package/dist/client/index.d.ts.map +1 -1
  173. package/dist/client/index.js.map +1 -1
  174. package/dist/component/_generated/component.d.ts +9 -0
  175. package/dist/component/_generated/component.d.ts.map +1 -1
  176. package/dist/component/mediaAssets.d.ts +35 -0
  177. package/dist/component/mediaAssets.d.ts.map +1 -1
  178. package/dist/component/mediaAssets.js +81 -0
  179. package/dist/component/mediaAssets.js.map +1 -1
  180. package/dist/test.d.ts.map +1 -1
  181. package/dist/test.js +2 -1
  182. package/dist/test.js.map +1 -1
  183. package/package.json +9 -5
  184. package/dist/component/auditLog.d.ts +0 -410
  185. package/dist/component/auditLog.d.ts.map +0 -1
  186. package/dist/component/auditLog.js +0 -607
  187. package/dist/component/auditLog.js.map +0 -1
  188. package/dist/component/types.d.ts +0 -4
  189. package/dist/component/types.d.ts.map +0 -1
  190. package/dist/component/types.js +0 -2
  191. package/dist/component/types.js.map +0 -1
  192. package/src/cli/commands/admin.ts +0 -104
  193. package/src/cli/index.ts +0 -21
  194. package/src/cli/utils/detectConvexUrl.ts +0 -54
  195. package/src/cli/utils/openBrowser.ts +0 -16
  196. package/src/client/admin-config.ts +0 -138
  197. package/src/client/adminApi.ts +0 -942
  198. package/src/client/agentTools.ts +0 -1311
  199. package/src/client/argTypes.ts +0 -316
  200. package/src/client/field-types.ts +0 -187
  201. package/src/client/index.ts +0 -1301
  202. package/src/client/queryBuilder.ts +0 -1100
  203. package/src/client/schema/codegen.ts +0 -500
  204. package/src/client/schema/defineContentType.ts +0 -501
  205. package/src/client/schema/index.ts +0 -169
  206. package/src/client/schema/schemaDrift.ts +0 -574
  207. package/src/client/schema/typedClient.ts +0 -688
  208. package/src/client/schema/types.ts +0 -666
  209. package/src/client/types.ts +0 -723
  210. package/src/client/workflows.ts +0 -141
  211. package/src/client/wrapper.ts +0 -4304
  212. package/src/component/_generated/api.ts +0 -140
  213. package/src/component/_generated/component.ts +0 -5029
  214. package/src/component/_generated/dataModel.ts +0 -60
  215. package/src/component/_generated/server.ts +0 -156
  216. package/src/component/authorization.ts +0 -647
  217. package/src/component/authorizationHooks.ts +0 -668
  218. package/src/component/bulkOperations.ts +0 -687
  219. package/src/component/contentEntries.ts +0 -1976
  220. package/src/component/contentEntryMutations.ts +0 -1223
  221. package/src/component/contentEntryValidation.ts +0 -707
  222. package/src/component/contentLock.ts +0 -550
  223. package/src/component/contentTypeMigration.ts +0 -1064
  224. package/src/component/contentTypeMutations.ts +0 -969
  225. package/src/component/contentTypes.ts +0 -346
  226. package/src/component/convex.config.ts +0 -44
  227. package/src/component/documentTypes.ts +0 -240
  228. package/src/component/eventEmitter.ts +0 -485
  229. package/src/component/exportImport.ts +0 -1169
  230. package/src/component/index.ts +0 -491
  231. package/src/component/lib/deepReferenceResolver.ts +0 -999
  232. package/src/component/lib/errors.ts +0 -816
  233. package/src/component/lib/index.ts +0 -145
  234. package/src/component/lib/mediaReferenceResolver.ts +0 -495
  235. package/src/component/lib/metadataExtractor.ts +0 -792
  236. package/src/component/lib/mutationAuth.ts +0 -199
  237. package/src/component/lib/queries.ts +0 -79
  238. package/src/component/lib/ragContentChunker.ts +0 -1371
  239. package/src/component/lib/referenceResolver.ts +0 -430
  240. package/src/component/lib/slugGenerator.ts +0 -262
  241. package/src/component/lib/slugUniqueness.ts +0 -333
  242. package/src/component/lib/softDelete.ts +0 -44
  243. package/src/component/localeFallbackChain.ts +0 -673
  244. package/src/component/localeFields.ts +0 -896
  245. package/src/component/mediaAssetMutations.ts +0 -725
  246. package/src/component/mediaAssets.ts +0 -932
  247. package/src/component/mediaFolderMutations.ts +0 -1046
  248. package/src/component/mediaUploadMutations.ts +0 -224
  249. package/src/component/mediaVariantMutations.ts +0 -900
  250. package/src/component/mediaVariants.ts +0 -793
  251. package/src/component/ragContentIndexer.ts +0 -1067
  252. package/src/component/rateLimitHooks.ts +0 -572
  253. package/src/component/roles.ts +0 -1360
  254. package/src/component/scheduledPublish.ts +0 -358
  255. package/src/component/schema.ts +0 -617
  256. package/src/component/taxonomies.ts +0 -949
  257. package/src/component/taxonomyMutations.ts +0 -1210
  258. package/src/component/trash.ts +0 -724
  259. package/src/component/userContext.ts +0 -898
  260. package/src/component/validation.ts +0 -1388
  261. package/src/component/validators.ts +0 -949
  262. package/src/component/versionMutations.ts +0 -392
  263. package/src/component/webhookTrigger.ts +0 -1922
  264. package/src/react/index.ts +0 -898
  265. package/src/test.ts +0 -1580
@@ -1,572 +0,0 @@
1
- /**
2
- * Rate Limit Hooks Execution Module
3
- *
4
- * This module provides the infrastructure for executing rate limit hooks
5
- * that enable custom rate limiting logic for CMS operations.
6
- *
7
- * Rate limiting hooks allow parent applications to:
8
- * - Implement custom rate limiting strategies (token bucket, fixed window, etc.)
9
- * - Define different limits for different user tiers
10
- * - Apply limits per operation, category, or globally
11
- * - Integrate with external rate limiting services
12
- *
13
- * Hook execution order:
14
- * 1. getConfig hook (optional) - Get dynamic rate limit configuration
15
- * 2. check hook - Check if operation is rate limited
16
- * 3. consume hook - Record rate limit usage (only if check passed)
17
- *
18
- * @example
19
- * ```typescript
20
- * import { executeRateLimitHooks, RateLimitedError } from './rateLimitHooks';
21
- *
22
- * // In a mutation handler
23
- * const rateLimitResult = await executeRateLimitHooks({
24
- * hooks: config.rateLimitHooks,
25
- * context: {
26
- * operation: 'contentEntries.create',
27
- * operationCategory: 'write',
28
- * userId: args.createdBy,
29
- * role: userRole,
30
- * timestamp: Date.now(),
31
- * },
32
- * });
33
- *
34
- * if (!rateLimitResult.allowed) {
35
- * throw new RateLimitedError(rateLimitResult.reason ?? 'Rate limit exceeded', {
36
- * retryAt: rateLimitResult.retryAt,
37
- * });
38
- * }
39
- * ```
40
- */
41
-
42
- import type {
43
- RateLimitHooks,
44
- RateLimitHookContext,
45
- RateLimitCheckResult,
46
- RateLimitConsumeResult,
47
- RateLimitConfigResult,
48
- CmsOperation,
49
- OperationCategory,
50
- } from "../client/types.js";
51
-
52
- // =============================================================================
53
- // Types
54
- // =============================================================================
55
-
56
- /**
57
- * Options for executing the rate limit hook chain.
58
- */
59
- export interface ExecuteRateLimitOptions {
60
- /**
61
- * The rate limit hooks configuration from ComponentConfig.
62
- */
63
- hooks?: RateLimitHooks;
64
-
65
- /**
66
- * The context for this rate limit check.
67
- */
68
- context: RateLimitHookContext;
69
- }
70
-
71
- /**
72
- * Result from executing the rate limit hook chain.
73
- */
74
- export interface RateLimitResult {
75
- /**
76
- * Whether the operation is allowed (not rate limited).
77
- */
78
- allowed: boolean;
79
-
80
- /**
81
- * The timestamp when the operation can be retried (if rate limited).
82
- * In milliseconds since epoch.
83
- */
84
- retryAt?: number;
85
-
86
- /**
87
- * The reason for rate limiting (if rate limited).
88
- */
89
- reason?: string;
90
-
91
- /**
92
- * Whether rate limiting was skipped entirely.
93
- * True if no hooks were configured, operation was excluded, or user was exempt.
94
- */
95
- skipped: boolean;
96
-
97
- /**
98
- * Information about the rate limit state.
99
- */
100
- rateLimitInfo?: RateLimitCheckResult["rateLimitInfo"];
101
-
102
- /**
103
- * The configuration that was used for rate limiting.
104
- */
105
- config?: RateLimitConfigResult;
106
- }
107
-
108
- /**
109
- * Error thrown when a rate limit is exceeded.
110
- */
111
- export class RateLimitedError extends Error {
112
- readonly code = "RATE_LIMITED";
113
- readonly retryAt?: number;
114
- readonly operation?: CmsOperation;
115
- readonly operationCategory?: OperationCategory;
116
- readonly rateLimitInfo?: RateLimitCheckResult["rateLimitInfo"];
117
-
118
- constructor(
119
- message: string,
120
- options?: {
121
- retryAt?: number;
122
- operation?: CmsOperation;
123
- operationCategory?: OperationCategory;
124
- rateLimitInfo?: RateLimitCheckResult["rateLimitInfo"];
125
- }
126
- ) {
127
- super(message);
128
- this.name = "RateLimitedError";
129
- this.retryAt = options?.retryAt;
130
- this.operation = options?.operation;
131
- this.operationCategory = options?.operationCategory;
132
- this.rateLimitInfo = options?.rateLimitInfo;
133
- }
134
-
135
- /**
136
- * Returns a human-readable message with retry information.
137
- */
138
- toUserMessage(): string {
139
- if (this.retryAt) {
140
- const retryInMs = this.retryAt - Date.now();
141
- if (retryInMs > 0) {
142
- const retryInSeconds = Math.ceil(retryInMs / 1000);
143
- return `${this.message}. Please retry in ${retryInSeconds} second${retryInSeconds !== 1 ? "s" : ""}.`;
144
- }
145
- }
146
- return this.message;
147
- }
148
- }
149
-
150
- // =============================================================================
151
- // Helper Functions
152
- // =============================================================================
153
-
154
- /**
155
- * Maps a CMS operation to its category.
156
- */
157
- export function operationToCategory(operation: CmsOperation): OperationCategory {
158
- // Read operations
159
- if (operation.endsWith(".read") || operation === "versions.read") {
160
- return "read";
161
- }
162
-
163
- // Publish operations
164
- if (
165
- operation === "contentEntries.publish" ||
166
- operation === "contentEntries.unpublish" ||
167
- operation === "contentEntries.schedule"
168
- ) {
169
- return "publish";
170
- }
171
-
172
- // Media operations
173
- if (
174
- operation.startsWith("mediaAssets.") ||
175
- operation.startsWith("mediaFolders.")
176
- ) {
177
- return "media";
178
- }
179
-
180
- // Admin operations (content type management)
181
- if (operation.startsWith("contentTypes.")) {
182
- return "admin";
183
- }
184
-
185
- // Write operations (default for mutations)
186
- return "write";
187
- }
188
-
189
- /**
190
- * Creates a rate limit context for a CMS operation.
191
- */
192
- export function createRateLimitContext(
193
- operation: CmsOperation,
194
- options: {
195
- userId?: string;
196
- role?: string | null;
197
- contentTypeId?: string;
198
- contentTypeName?: string;
199
- metadata?: Record<string, unknown>;
200
- }
201
- ): RateLimitHookContext {
202
- return {
203
- operation,
204
- operationCategory: operationToCategory(operation),
205
- userId: options.userId,
206
- role: options.role,
207
- contentTypeId: options.contentTypeId,
208
- contentTypeName: options.contentTypeName,
209
- metadata: options.metadata,
210
- timestamp: Date.now(),
211
- };
212
- }
213
-
214
- /**
215
- * Execute a single rate limit hook safely.
216
- * Catches errors and converts them to allowed results to avoid blocking operations.
217
- */
218
- async function executeCheckHook(
219
- hook: ((context: RateLimitHookContext) => Promise<RateLimitCheckResult> | RateLimitCheckResult) | undefined,
220
- context: RateLimitHookContext
221
- ): Promise<RateLimitCheckResult> {
222
- if (!hook) {
223
- return { allowed: true };
224
- }
225
-
226
- try {
227
- return await hook(context);
228
- } catch (error) {
229
- // Hook threw an error - log and allow operation to proceed
230
- // This prevents rate limit hook errors from blocking legitimate operations
231
- console.error("Rate limit check hook error:", error);
232
- return { allowed: true };
233
- }
234
- }
235
-
236
- /**
237
- * Execute the consume hook safely.
238
- */
239
- async function executeConsumeHook(
240
- hook: ((context: RateLimitHookContext) => Promise<RateLimitConsumeResult> | RateLimitConsumeResult) | undefined,
241
- context: RateLimitHookContext
242
- ): Promise<RateLimitConsumeResult> {
243
- if (!hook) {
244
- return { allowed: true, consumed: false };
245
- }
246
-
247
- try {
248
- return await hook(context);
249
- } catch (error) {
250
- // Hook threw an error - log and allow operation to proceed
251
- console.error("Rate limit consume hook error:", error);
252
- return { allowed: true, consumed: false };
253
- }
254
- }
255
-
256
- /**
257
- * Execute the config hook safely.
258
- */
259
- async function executeConfigHook(
260
- hook: ((context: RateLimitHookContext) => Promise<RateLimitConfigResult> | RateLimitConfigResult) | undefined,
261
- context: RateLimitHookContext
262
- ): Promise<RateLimitConfigResult | undefined> {
263
- if (!hook) {
264
- return undefined;
265
- }
266
-
267
- try {
268
- return await hook(context);
269
- } catch (error) {
270
- console.error("Rate limit config hook error:", error);
271
- return undefined;
272
- }
273
- }
274
-
275
- // =============================================================================
276
- // Main Rate Limit Execution Function
277
- // =============================================================================
278
-
279
- /**
280
- * Execute the full rate limit hook chain for an operation.
281
- *
282
- * This function orchestrates the execution of rate limit hooks in the correct order:
283
- *
284
- * 1. Check if rate limiting should be skipped (no hooks, excluded operation/category, admin bypass)
285
- * 2. Get configuration from getConfig hook (if provided)
286
- * 3. Execute check hook (or operation-specific override)
287
- * 4. Execute consume hook if check passed (or operation-specific override)
288
- * 5. Call onRateLimited callback if operation was denied
289
- *
290
- * @param options - Configuration for the rate limit execution
291
- * @returns RateLimitResult indicating if the operation is allowed
292
- *
293
- * @example
294
- * ```typescript
295
- * const result = await executeRateLimitHooks({
296
- * hooks: config.rateLimitHooks,
297
- * context: {
298
- * operation: 'contentEntries.create',
299
- * operationCategory: 'write',
300
- * userId: 'user123',
301
- * role: 'editor',
302
- * timestamp: Date.now(),
303
- * },
304
- * });
305
- *
306
- * if (!result.allowed) {
307
- * throw new RateLimitedError(result.reason ?? 'Rate limit exceeded', {
308
- * retryAt: result.retryAt,
309
- * });
310
- * }
311
- * ```
312
- */
313
- export async function executeRateLimitHooks(
314
- options: ExecuteRateLimitOptions
315
- ): Promise<RateLimitResult> {
316
- const { hooks, context } = options;
317
-
318
- // -------------------------------------------------------------------------
319
- // Step 1: Check if rate limiting should be skipped
320
- // -------------------------------------------------------------------------
321
-
322
- // No hooks configured - skip rate limiting
323
- if (!hooks || (!hooks.check && !hooks.consume && !hooks.operationHooks)) {
324
- return {
325
- allowed: true,
326
- skipped: true,
327
- };
328
- }
329
-
330
- // Check if operation is excluded
331
- if (hooks.excludeOperations?.includes(context.operation)) {
332
- return {
333
- allowed: true,
334
- skipped: true,
335
- };
336
- }
337
-
338
- // Check if category is excluded
339
- if (hooks.excludeCategories?.includes(context.operationCategory)) {
340
- return {
341
- allowed: true,
342
- skipped: true,
343
- };
344
- }
345
-
346
- // Check if admin users should be exempted
347
- if (hooks.skipForAdmin && context.role === "admin") {
348
- return {
349
- allowed: true,
350
- skipped: true,
351
- };
352
- }
353
-
354
- // -------------------------------------------------------------------------
355
- // Step 2: Get operation-specific hooks or use global hooks
356
- // -------------------------------------------------------------------------
357
- const operationHooks = hooks.operationHooks?.[context.operation];
358
- const checkHook = operationHooks?.check ?? hooks.check;
359
- const consumeHook = operationHooks?.consume ?? hooks.consume;
360
- const configHook = operationHooks?.getConfig ?? hooks.getConfig;
361
-
362
- // No check hook configured - skip rate limiting
363
- if (!checkHook) {
364
- return {
365
- allowed: true,
366
- skipped: true,
367
- };
368
- }
369
-
370
- // -------------------------------------------------------------------------
371
- // Step 3: Get configuration (optional)
372
- // -------------------------------------------------------------------------
373
- const configResult = await executeConfigHook(configHook, context);
374
-
375
- // Config hook says rate limiting is disabled
376
- if (configResult && !configResult.enabled) {
377
- return {
378
- allowed: true,
379
- skipped: true,
380
- config: configResult,
381
- };
382
- }
383
-
384
- // -------------------------------------------------------------------------
385
- // Step 4: Execute check hook
386
- // -------------------------------------------------------------------------
387
- const checkResult = await executeCheckHook(checkHook, context);
388
-
389
- if (!checkResult.allowed) {
390
- // Rate limit exceeded - call onRateLimited callback
391
- if (hooks.onRateLimited) {
392
- try {
393
- await hooks.onRateLimited(context, checkResult);
394
- } catch (error) {
395
- // Don't let callback errors affect the result
396
- console.error("onRateLimited callback error:", error);
397
- }
398
- }
399
-
400
- return {
401
- allowed: false,
402
- retryAt: checkResult.retryAt,
403
- reason: checkResult.reason ?? "Rate limit exceeded",
404
- skipped: false,
405
- rateLimitInfo: checkResult.rateLimitInfo,
406
- config: configResult,
407
- };
408
- }
409
-
410
- // -------------------------------------------------------------------------
411
- // Step 5: Execute consume hook (if provided)
412
- // -------------------------------------------------------------------------
413
- // If no separate consume hook, the check hook is assumed to have consumed
414
- if (consumeHook) {
415
- const consumeResult = await executeConsumeHook(consumeHook, context);
416
-
417
- if (!consumeResult.allowed) {
418
- // Consume failed (another request beat us to the limit)
419
- if (hooks.onRateLimited) {
420
- try {
421
- await hooks.onRateLimited(context, consumeResult);
422
- } catch (error) {
423
- console.error("onRateLimited callback error:", error);
424
- }
425
- }
426
-
427
- return {
428
- allowed: false,
429
- retryAt: consumeResult.retryAt,
430
- reason: consumeResult.reason ?? "Rate limit exceeded",
431
- skipped: false,
432
- rateLimitInfo: consumeResult.rateLimitInfo,
433
- config: configResult,
434
- };
435
- }
436
- }
437
-
438
- // -------------------------------------------------------------------------
439
- // All checks passed
440
- // -------------------------------------------------------------------------
441
- return {
442
- allowed: true,
443
- skipped: false,
444
- rateLimitInfo: checkResult.rateLimitInfo,
445
- config: configResult,
446
- };
447
- }
448
-
449
- /**
450
- * Execute rate limit hooks and throw RateLimitedError if rate limited.
451
- *
452
- * This is a convenience wrapper that executes hooks and throws
453
- * RateLimitedError if the operation is rate limited.
454
- *
455
- * @param options - Rate limit execution options
456
- * @throws RateLimitedError if the operation is rate limited
457
- * @returns The rate limit result (if allowed)
458
- */
459
- export async function requireRateLimit(
460
- options: ExecuteRateLimitOptions
461
- ): Promise<RateLimitResult> {
462
- const result = await executeRateLimitHooks(options);
463
-
464
- if (!result.allowed) {
465
- throw new RateLimitedError(result.reason ?? "Rate limit exceeded", {
466
- retryAt: result.retryAt,
467
- operation: options.context.operation,
468
- operationCategory: options.context.operationCategory,
469
- rateLimitInfo: result.rateLimitInfo,
470
- });
471
- }
472
-
473
- return result;
474
- }
475
-
476
- // =============================================================================
477
- // Utility Functions for Building Rate Limit Hooks
478
- // =============================================================================
479
-
480
- /**
481
- * Creates a simple rate limit key from context.
482
- * Default format: `{userId}:{operationCategory}`
483
- */
484
- export function createRateLimitKey(
485
- context: RateLimitHookContext,
486
- options?: {
487
- /** Include operation in key (more granular limiting) */
488
- includeOperation?: boolean;
489
- /** Include content type in key */
490
- includeContentType?: boolean;
491
- /** Custom prefix */
492
- prefix?: string;
493
- }
494
- ): string {
495
- const parts: string[] = [];
496
-
497
- if (options?.prefix) {
498
- parts.push(options.prefix);
499
- }
500
-
501
- if (context.userId) {
502
- parts.push(context.userId);
503
- } else {
504
- parts.push("anonymous");
505
- }
506
-
507
- if (options?.includeOperation) {
508
- parts.push(context.operation);
509
- } else {
510
- parts.push(context.operationCategory);
511
- }
512
-
513
- if (options?.includeContentType && context.contentTypeName) {
514
- parts.push(context.contentTypeName);
515
- }
516
-
517
- return parts.join(":");
518
- }
519
-
520
- /**
521
- * Creates a rate limit name from operation category.
522
- * Useful for mapping to named rate limits in convex-helpers.
523
- */
524
- export function createRateLimitName(
525
- context: RateLimitHookContext,
526
- prefix = "cms"
527
- ): string {
528
- return `${prefix}.${context.operationCategory}`;
529
- }
530
-
531
- /**
532
- * Default tier-based rate limit configurations.
533
- * Can be used as a starting point for custom configurations.
534
- */
535
- export const DEFAULT_TIER_LIMITS = {
536
- free: {
537
- read: { rate: 100, period: 60000 }, // 100 reads per minute
538
- write: { rate: 10, period: 60000 }, // 10 writes per minute
539
- publish: { rate: 5, period: 60000 }, // 5 publishes per minute
540
- media: { rate: 10, period: 60000 }, // 10 media ops per minute
541
- admin: { rate: 5, period: 60000 }, // 5 admin ops per minute
542
- },
543
- pro: {
544
- read: { rate: 500, period: 60000 },
545
- write: { rate: 50, period: 60000 },
546
- publish: { rate: 20, period: 60000 },
547
- media: { rate: 50, period: 60000 },
548
- admin: { rate: 20, period: 60000 },
549
- },
550
- enterprise: {
551
- read: { rate: 2000, period: 60000 },
552
- write: { rate: 200, period: 60000 },
553
- publish: { rate: 100, period: 60000 },
554
- media: { rate: 200, period: 60000 },
555
- admin: { rate: 100, period: 60000 },
556
- },
557
- } as const;
558
-
559
- /**
560
- * Type for user tiers.
561
- */
562
- export type UserTier = keyof typeof DEFAULT_TIER_LIMITS;
563
-
564
- /**
565
- * Get rate limit configuration for a user tier and operation category.
566
- */
567
- export function getTierLimit(
568
- tier: UserTier,
569
- category: OperationCategory
570
- ): { rate: number; period: number } {
571
- return DEFAULT_TIER_LIMITS[tier][category];
572
- }