convex-cms 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/dist/cli/commands/admin.d.ts +16 -0
  2. package/dist/cli/commands/admin.d.ts.map +1 -0
  3. package/dist/cli/commands/admin.js +88 -0
  4. package/dist/cli/commands/admin.js.map +1 -0
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -0
  7. package/dist/cli/index.js +18 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/utils/detectConvexUrl.d.ts +13 -0
  10. package/dist/cli/utils/detectConvexUrl.d.ts.map +1 -0
  11. package/dist/cli/utils/detectConvexUrl.js +48 -0
  12. package/dist/cli/utils/detectConvexUrl.js.map +1 -0
  13. package/dist/cli/utils/openBrowser.d.ts +7 -0
  14. package/dist/cli/utils/openBrowser.d.ts.map +1 -0
  15. package/dist/cli/utils/openBrowser.js +17 -0
  16. package/dist/cli/utils/openBrowser.js.map +1 -0
  17. package/dist/client/admin-config.d.ts +126 -0
  18. package/dist/client/admin-config.d.ts.map +1 -0
  19. package/dist/client/admin-config.js +117 -0
  20. package/dist/client/admin-config.js.map +1 -0
  21. package/dist/client/adminApi.d.ts +2273 -0
  22. package/dist/client/adminApi.d.ts.map +1 -0
  23. package/dist/client/adminApi.js +716 -0
  24. package/dist/client/adminApi.js.map +1 -0
  25. package/dist/client/agentTools.d.ts +933 -0
  26. package/dist/client/agentTools.d.ts.map +1 -0
  27. package/dist/client/agentTools.js +1004 -0
  28. package/dist/client/agentTools.js.map +1 -0
  29. package/dist/client/argTypes.d.ts +212 -0
  30. package/dist/client/argTypes.d.ts.map +1 -0
  31. package/dist/client/argTypes.js +5 -0
  32. package/dist/client/argTypes.js.map +1 -0
  33. package/dist/client/field-types.d.ts +55 -0
  34. package/dist/client/field-types.d.ts.map +1 -0
  35. package/dist/client/field-types.js +152 -0
  36. package/dist/client/field-types.js.map +1 -0
  37. package/dist/client/index.d.ts +189 -0
  38. package/dist/client/index.d.ts.map +1 -0
  39. package/dist/client/index.js +668 -0
  40. package/dist/client/index.js.map +1 -0
  41. package/dist/client/queryBuilder.d.ts +765 -0
  42. package/dist/client/queryBuilder.d.ts.map +1 -0
  43. package/dist/client/queryBuilder.js +970 -0
  44. package/dist/client/queryBuilder.js.map +1 -0
  45. package/dist/client/schema/codegen.d.ts +128 -0
  46. package/dist/client/schema/codegen.d.ts.map +1 -0
  47. package/dist/client/schema/codegen.js +318 -0
  48. package/dist/client/schema/codegen.js.map +1 -0
  49. package/dist/client/schema/defineContentType.d.ts +221 -0
  50. package/dist/client/schema/defineContentType.d.ts.map +1 -0
  51. package/dist/client/schema/defineContentType.js +380 -0
  52. package/dist/client/schema/defineContentType.js.map +1 -0
  53. package/dist/client/schema/index.d.ts +85 -0
  54. package/dist/client/schema/index.d.ts.map +1 -0
  55. package/dist/client/schema/index.js +92 -0
  56. package/dist/client/schema/index.js.map +1 -0
  57. package/dist/client/schema/schemaDrift.d.ts +199 -0
  58. package/dist/client/schema/schemaDrift.d.ts.map +1 -0
  59. package/dist/client/schema/schemaDrift.js +340 -0
  60. package/dist/client/schema/schemaDrift.js.map +1 -0
  61. package/dist/client/schema/typedClient.d.ts +401 -0
  62. package/dist/client/schema/typedClient.d.ts.map +1 -0
  63. package/dist/client/schema/typedClient.js +269 -0
  64. package/dist/client/schema/typedClient.js.map +1 -0
  65. package/dist/client/schema/types.d.ts +477 -0
  66. package/dist/client/schema/types.d.ts.map +1 -0
  67. package/dist/client/schema/types.js +39 -0
  68. package/dist/client/schema/types.js.map +1 -0
  69. package/dist/client/types.d.ts +449 -0
  70. package/dist/client/types.d.ts.map +1 -0
  71. package/dist/client/types.js +149 -0
  72. package/dist/client/types.js.map +1 -0
  73. package/dist/client/workflows.d.ts +51 -0
  74. package/dist/client/workflows.d.ts.map +1 -0
  75. package/dist/client/workflows.js +103 -0
  76. package/dist/client/workflows.js.map +1 -0
  77. package/dist/client/wrapper.d.ts +2198 -0
  78. package/dist/client/wrapper.d.ts.map +1 -0
  79. package/dist/client/wrapper.js +2651 -0
  80. package/dist/client/wrapper.js.map +1 -0
  81. package/dist/component/_generated/api.d.ts +124 -0
  82. package/dist/component/_generated/api.d.ts.map +1 -0
  83. package/dist/component/_generated/api.js +31 -0
  84. package/dist/component/_generated/api.js.map +1 -0
  85. package/dist/component/_generated/component.d.ts +4321 -0
  86. package/dist/component/_generated/component.d.ts.map +1 -0
  87. package/dist/component/_generated/component.js +11 -0
  88. package/dist/component/_generated/component.js.map +1 -0
  89. package/dist/component/_generated/dataModel.d.ts +46 -0
  90. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  91. package/dist/component/_generated/dataModel.js +11 -0
  92. package/dist/component/_generated/dataModel.js.map +1 -0
  93. package/dist/component/_generated/server.d.ts +121 -0
  94. package/dist/component/_generated/server.d.ts.map +1 -0
  95. package/dist/component/_generated/server.js +78 -0
  96. package/dist/component/_generated/server.js.map +1 -0
  97. package/dist/component/auditLog.d.ts +410 -0
  98. package/dist/component/auditLog.d.ts.map +1 -0
  99. package/dist/component/auditLog.js +607 -0
  100. package/dist/component/auditLog.js.map +1 -0
  101. package/dist/component/authorization.d.ts +323 -0
  102. package/dist/component/authorization.d.ts.map +1 -0
  103. package/dist/component/authorization.js +464 -0
  104. package/dist/component/authorization.js.map +1 -0
  105. package/dist/component/authorizationHooks.d.ts +184 -0
  106. package/dist/component/authorizationHooks.d.ts.map +1 -0
  107. package/dist/component/authorizationHooks.js +521 -0
  108. package/dist/component/authorizationHooks.js.map +1 -0
  109. package/dist/component/bulkOperations.d.ts +200 -0
  110. package/dist/component/bulkOperations.d.ts.map +1 -0
  111. package/dist/component/bulkOperations.js +568 -0
  112. package/dist/component/bulkOperations.js.map +1 -0
  113. package/dist/component/contentEntries.d.ts +719 -0
  114. package/dist/component/contentEntries.d.ts.map +1 -0
  115. package/dist/component/contentEntries.js +1617 -0
  116. package/dist/component/contentEntries.js.map +1 -0
  117. package/dist/component/contentEntryMutations.d.ts +505 -0
  118. package/dist/component/contentEntryMutations.d.ts.map +1 -0
  119. package/dist/component/contentEntryMutations.js +1009 -0
  120. package/dist/component/contentEntryMutations.js.map +1 -0
  121. package/dist/component/contentEntryValidation.d.ts +115 -0
  122. package/dist/component/contentEntryValidation.d.ts.map +1 -0
  123. package/dist/component/contentEntryValidation.js +546 -0
  124. package/dist/component/contentEntryValidation.js.map +1 -0
  125. package/dist/component/contentLock.d.ts +328 -0
  126. package/dist/component/contentLock.d.ts.map +1 -0
  127. package/dist/component/contentLock.js +471 -0
  128. package/dist/component/contentLock.js.map +1 -0
  129. package/dist/component/contentTypeMigration.d.ts +411 -0
  130. package/dist/component/contentTypeMigration.d.ts.map +1 -0
  131. package/dist/component/contentTypeMigration.js +805 -0
  132. package/dist/component/contentTypeMigration.js.map +1 -0
  133. package/dist/component/contentTypeMutations.d.ts +975 -0
  134. package/dist/component/contentTypeMutations.d.ts.map +1 -0
  135. package/dist/component/contentTypeMutations.js +768 -0
  136. package/dist/component/contentTypeMutations.js.map +1 -0
  137. package/dist/component/contentTypes.d.ts +538 -0
  138. package/dist/component/contentTypes.d.ts.map +1 -0
  139. package/dist/component/contentTypes.js +304 -0
  140. package/dist/component/contentTypes.js.map +1 -0
  141. package/dist/component/convex.config.d.ts +42 -0
  142. package/dist/component/convex.config.d.ts.map +1 -0
  143. package/dist/component/convex.config.js +43 -0
  144. package/dist/component/convex.config.js.map +1 -0
  145. package/dist/component/documentTypes.d.ts +186 -0
  146. package/dist/component/documentTypes.d.ts.map +1 -0
  147. package/dist/component/documentTypes.js +23 -0
  148. package/dist/component/documentTypes.js.map +1 -0
  149. package/dist/component/eventEmitter.d.ts +281 -0
  150. package/dist/component/eventEmitter.d.ts.map +1 -0
  151. package/dist/component/eventEmitter.js +300 -0
  152. package/dist/component/eventEmitter.js.map +1 -0
  153. package/dist/component/exportImport.d.ts +1120 -0
  154. package/dist/component/exportImport.d.ts.map +1 -0
  155. package/dist/component/exportImport.js +931 -0
  156. package/dist/component/exportImport.js.map +1 -0
  157. package/dist/component/index.d.ts +28 -0
  158. package/dist/component/index.d.ts.map +1 -0
  159. package/dist/component/index.js +142 -0
  160. package/dist/component/index.js.map +1 -0
  161. package/dist/component/lib/deepReferenceResolver.d.ts +252 -0
  162. package/dist/component/lib/deepReferenceResolver.d.ts.map +1 -0
  163. package/dist/component/lib/deepReferenceResolver.js +601 -0
  164. package/dist/component/lib/deepReferenceResolver.js.map +1 -0
  165. package/dist/component/lib/errors.d.ts +306 -0
  166. package/dist/component/lib/errors.d.ts.map +1 -0
  167. package/dist/component/lib/errors.js +407 -0
  168. package/dist/component/lib/errors.js.map +1 -0
  169. package/dist/component/lib/index.d.ts +10 -0
  170. package/dist/component/lib/index.d.ts.map +1 -0
  171. package/dist/component/lib/index.js +33 -0
  172. package/dist/component/lib/index.js.map +1 -0
  173. package/dist/component/lib/mediaReferenceResolver.d.ts +217 -0
  174. package/dist/component/lib/mediaReferenceResolver.d.ts.map +1 -0
  175. package/dist/component/lib/mediaReferenceResolver.js +326 -0
  176. package/dist/component/lib/mediaReferenceResolver.js.map +1 -0
  177. package/dist/component/lib/metadataExtractor.d.ts +245 -0
  178. package/dist/component/lib/metadataExtractor.d.ts.map +1 -0
  179. package/dist/component/lib/metadataExtractor.js +548 -0
  180. package/dist/component/lib/metadataExtractor.js.map +1 -0
  181. package/dist/component/lib/mutationAuth.d.ts +95 -0
  182. package/dist/component/lib/mutationAuth.d.ts.map +1 -0
  183. package/dist/component/lib/mutationAuth.js +146 -0
  184. package/dist/component/lib/mutationAuth.js.map +1 -0
  185. package/dist/component/lib/queries.d.ts +17 -0
  186. package/dist/component/lib/queries.d.ts.map +1 -0
  187. package/dist/component/lib/queries.js +49 -0
  188. package/dist/component/lib/queries.js.map +1 -0
  189. package/dist/component/lib/ragContentChunker.d.ts +423 -0
  190. package/dist/component/lib/ragContentChunker.d.ts.map +1 -0
  191. package/dist/component/lib/ragContentChunker.js +897 -0
  192. package/dist/component/lib/ragContentChunker.js.map +1 -0
  193. package/dist/component/lib/referenceResolver.d.ts +175 -0
  194. package/dist/component/lib/referenceResolver.d.ts.map +1 -0
  195. package/dist/component/lib/referenceResolver.js +293 -0
  196. package/dist/component/lib/referenceResolver.js.map +1 -0
  197. package/dist/component/lib/slugGenerator.d.ts +71 -0
  198. package/dist/component/lib/slugGenerator.d.ts.map +1 -0
  199. package/dist/component/lib/slugGenerator.js +207 -0
  200. package/dist/component/lib/slugGenerator.js.map +1 -0
  201. package/dist/component/lib/slugUniqueness.d.ts +131 -0
  202. package/dist/component/lib/slugUniqueness.d.ts.map +1 -0
  203. package/dist/component/lib/slugUniqueness.js +229 -0
  204. package/dist/component/lib/slugUniqueness.js.map +1 -0
  205. package/dist/component/lib/softDelete.d.ts +18 -0
  206. package/dist/component/lib/softDelete.d.ts.map +1 -0
  207. package/dist/component/lib/softDelete.js +29 -0
  208. package/dist/component/lib/softDelete.js.map +1 -0
  209. package/dist/component/localeFallbackChain.d.ts +410 -0
  210. package/dist/component/localeFallbackChain.d.ts.map +1 -0
  211. package/dist/component/localeFallbackChain.js +467 -0
  212. package/dist/component/localeFallbackChain.js.map +1 -0
  213. package/dist/component/localeFields.d.ts +508 -0
  214. package/dist/component/localeFields.d.ts.map +1 -0
  215. package/dist/component/localeFields.js +592 -0
  216. package/dist/component/localeFields.js.map +1 -0
  217. package/dist/component/mediaAssetMutations.d.ts +235 -0
  218. package/dist/component/mediaAssetMutations.d.ts.map +1 -0
  219. package/dist/component/mediaAssetMutations.js +558 -0
  220. package/dist/component/mediaAssetMutations.js.map +1 -0
  221. package/dist/component/mediaAssets.d.ts +168 -0
  222. package/dist/component/mediaAssets.d.ts.map +1 -0
  223. package/dist/component/mediaAssets.js +618 -0
  224. package/dist/component/mediaAssets.js.map +1 -0
  225. package/dist/component/mediaFolderMutations.d.ts +642 -0
  226. package/dist/component/mediaFolderMutations.d.ts.map +1 -0
  227. package/dist/component/mediaFolderMutations.js +849 -0
  228. package/dist/component/mediaFolderMutations.js.map +1 -0
  229. package/dist/component/mediaUploadMutations.d.ts +136 -0
  230. package/dist/component/mediaUploadMutations.d.ts.map +1 -0
  231. package/dist/component/mediaUploadMutations.js +205 -0
  232. package/dist/component/mediaUploadMutations.js.map +1 -0
  233. package/dist/component/mediaVariantMutations.d.ts +468 -0
  234. package/dist/component/mediaVariantMutations.d.ts.map +1 -0
  235. package/dist/component/mediaVariantMutations.js +737 -0
  236. package/dist/component/mediaVariantMutations.js.map +1 -0
  237. package/dist/component/mediaVariants.d.ts +525 -0
  238. package/dist/component/mediaVariants.d.ts.map +1 -0
  239. package/dist/component/mediaVariants.js +661 -0
  240. package/dist/component/mediaVariants.js.map +1 -0
  241. package/dist/component/ragContentIndexer.d.ts +595 -0
  242. package/dist/component/ragContentIndexer.d.ts.map +1 -0
  243. package/dist/component/ragContentIndexer.js +794 -0
  244. package/dist/component/ragContentIndexer.js.map +1 -0
  245. package/dist/component/rateLimitHooks.d.ts +266 -0
  246. package/dist/component/rateLimitHooks.d.ts.map +1 -0
  247. package/dist/component/rateLimitHooks.js +412 -0
  248. package/dist/component/rateLimitHooks.js.map +1 -0
  249. package/dist/component/roles.d.ts +649 -0
  250. package/dist/component/roles.d.ts.map +1 -0
  251. package/dist/component/roles.js +884 -0
  252. package/dist/component/roles.js.map +1 -0
  253. package/dist/component/scheduledPublish.d.ts +182 -0
  254. package/dist/component/scheduledPublish.d.ts.map +1 -0
  255. package/dist/component/scheduledPublish.js +304 -0
  256. package/dist/component/scheduledPublish.js.map +1 -0
  257. package/dist/component/schema.d.ts +4114 -0
  258. package/dist/component/schema.d.ts.map +1 -0
  259. package/dist/component/schema.js +469 -0
  260. package/dist/component/schema.js.map +1 -0
  261. package/dist/component/taxonomies.d.ts +476 -0
  262. package/dist/component/taxonomies.d.ts.map +1 -0
  263. package/dist/component/taxonomies.js +785 -0
  264. package/dist/component/taxonomies.js.map +1 -0
  265. package/dist/component/taxonomyMutations.d.ts +206 -0
  266. package/dist/component/taxonomyMutations.d.ts.map +1 -0
  267. package/dist/component/taxonomyMutations.js +1001 -0
  268. package/dist/component/taxonomyMutations.js.map +1 -0
  269. package/dist/component/trash.d.ts +265 -0
  270. package/dist/component/trash.d.ts.map +1 -0
  271. package/dist/component/trash.js +621 -0
  272. package/dist/component/trash.js.map +1 -0
  273. package/dist/component/types.d.ts +4 -0
  274. package/dist/component/types.d.ts.map +1 -0
  275. package/dist/component/types.js +2 -0
  276. package/dist/component/types.js.map +1 -0
  277. package/dist/component/userContext.d.ts +508 -0
  278. package/dist/component/userContext.d.ts.map +1 -0
  279. package/dist/component/userContext.js +615 -0
  280. package/dist/component/userContext.js.map +1 -0
  281. package/dist/component/validation.d.ts +387 -0
  282. package/dist/component/validation.d.ts.map +1 -0
  283. package/dist/component/validation.js +1052 -0
  284. package/dist/component/validation.js.map +1 -0
  285. package/dist/component/validators.d.ts +4645 -0
  286. package/dist/component/validators.d.ts.map +1 -0
  287. package/dist/component/validators.js +641 -0
  288. package/dist/component/validators.js.map +1 -0
  289. package/dist/component/versionMutations.d.ts +216 -0
  290. package/dist/component/versionMutations.d.ts.map +1 -0
  291. package/dist/component/versionMutations.js +321 -0
  292. package/dist/component/versionMutations.js.map +1 -0
  293. package/dist/component/webhookTrigger.d.ts +770 -0
  294. package/dist/component/webhookTrigger.d.ts.map +1 -0
  295. package/dist/component/webhookTrigger.js +1413 -0
  296. package/dist/component/webhookTrigger.js.map +1 -0
  297. package/dist/react/index.d.ts +316 -0
  298. package/dist/react/index.d.ts.map +1 -0
  299. package/dist/react/index.js +558 -0
  300. package/dist/react/index.js.map +1 -0
  301. package/dist/test.d.ts +2230 -0
  302. package/dist/test.d.ts.map +1 -0
  303. package/dist/test.js +1107 -0
  304. package/dist/test.js.map +1 -0
  305. package/package.json +95 -0
  306. package/src/cli/commands/admin.ts +104 -0
  307. package/src/cli/index.ts +21 -0
  308. package/src/cli/utils/detectConvexUrl.ts +54 -0
  309. package/src/cli/utils/openBrowser.ts +16 -0
  310. package/src/client/admin-config.ts +138 -0
  311. package/src/client/adminApi.ts +942 -0
  312. package/src/client/agentTools.ts +1311 -0
  313. package/src/client/argTypes.ts +316 -0
  314. package/src/client/field-types.ts +187 -0
  315. package/src/client/index.ts +1301 -0
  316. package/src/client/queryBuilder.ts +1100 -0
  317. package/src/client/schema/codegen.ts +500 -0
  318. package/src/client/schema/defineContentType.ts +501 -0
  319. package/src/client/schema/index.ts +169 -0
  320. package/src/client/schema/schemaDrift.ts +574 -0
  321. package/src/client/schema/typedClient.ts +688 -0
  322. package/src/client/schema/types.ts +666 -0
  323. package/src/client/types.ts +723 -0
  324. package/src/client/workflows.ts +141 -0
  325. package/src/client/wrapper.ts +4304 -0
  326. package/src/component/_generated/api.ts +140 -0
  327. package/src/component/_generated/component.ts +5029 -0
  328. package/src/component/_generated/dataModel.ts +60 -0
  329. package/src/component/_generated/server.ts +156 -0
  330. package/src/component/authorization.ts +647 -0
  331. package/src/component/authorizationHooks.ts +668 -0
  332. package/src/component/bulkOperations.ts +687 -0
  333. package/src/component/contentEntries.ts +1976 -0
  334. package/src/component/contentEntryMutations.ts +1223 -0
  335. package/src/component/contentEntryValidation.ts +707 -0
  336. package/src/component/contentLock.ts +550 -0
  337. package/src/component/contentTypeMigration.ts +1064 -0
  338. package/src/component/contentTypeMutations.ts +969 -0
  339. package/src/component/contentTypes.ts +346 -0
  340. package/src/component/convex.config.ts +44 -0
  341. package/src/component/documentTypes.ts +240 -0
  342. package/src/component/eventEmitter.ts +485 -0
  343. package/src/component/exportImport.ts +1169 -0
  344. package/src/component/index.ts +491 -0
  345. package/src/component/lib/deepReferenceResolver.ts +999 -0
  346. package/src/component/lib/errors.ts +816 -0
  347. package/src/component/lib/index.ts +145 -0
  348. package/src/component/lib/mediaReferenceResolver.ts +495 -0
  349. package/src/component/lib/metadataExtractor.ts +792 -0
  350. package/src/component/lib/mutationAuth.ts +199 -0
  351. package/src/component/lib/queries.ts +79 -0
  352. package/src/component/lib/ragContentChunker.ts +1371 -0
  353. package/src/component/lib/referenceResolver.ts +430 -0
  354. package/src/component/lib/slugGenerator.ts +262 -0
  355. package/src/component/lib/slugUniqueness.ts +333 -0
  356. package/src/component/lib/softDelete.ts +44 -0
  357. package/src/component/localeFallbackChain.ts +673 -0
  358. package/src/component/localeFields.ts +896 -0
  359. package/src/component/mediaAssetMutations.ts +725 -0
  360. package/src/component/mediaAssets.ts +932 -0
  361. package/src/component/mediaFolderMutations.ts +1046 -0
  362. package/src/component/mediaUploadMutations.ts +224 -0
  363. package/src/component/mediaVariantMutations.ts +900 -0
  364. package/src/component/mediaVariants.ts +793 -0
  365. package/src/component/ragContentIndexer.ts +1067 -0
  366. package/src/component/rateLimitHooks.ts +572 -0
  367. package/src/component/roles.ts +1360 -0
  368. package/src/component/scheduledPublish.ts +358 -0
  369. package/src/component/schema.ts +617 -0
  370. package/src/component/taxonomies.ts +949 -0
  371. package/src/component/taxonomyMutations.ts +1210 -0
  372. package/src/component/trash.ts +724 -0
  373. package/src/component/userContext.ts +898 -0
  374. package/src/component/validation.ts +1388 -0
  375. package/src/component/validators.ts +949 -0
  376. package/src/component/versionMutations.ts +392 -0
  377. package/src/component/webhookTrigger.ts +1922 -0
  378. package/src/react/index.ts +898 -0
  379. package/src/test.ts +1580 -0
@@ -0,0 +1,792 @@
1
+ /**
2
+ * Media Metadata Extractor
3
+ *
4
+ * Internal utility functions for extracting and normalizing metadata from uploaded files.
5
+ * This module provides type-safe metadata extraction based on file MIME types.
6
+ *
7
+ * Since Convex functions don't have direct file access (files are in storage),
8
+ * this extractor works with:
9
+ * 1. Client-provided metadata (dimensions, duration from browser APIs)
10
+ * 2. MIME type analysis for capability detection
11
+ * 3. Filename analysis for additional hints
12
+ *
13
+ * The extractor normalizes and validates this information into a structured format
14
+ * that can be stored in the media asset's metadata field.
15
+ */
16
+
17
+ // =============================================================================
18
+ // Type Definitions
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Base metadata properties common to all media types.
23
+ */
24
+ export interface BaseMetadata {
25
+ /** Original filename */
26
+ filename: string;
27
+ /** MIME type of the file */
28
+ mimeType: string;
29
+ /** File size in bytes */
30
+ size: number;
31
+ /** Classified media type */
32
+ mediaType: "image" | "video" | "audio" | "document" | "other";
33
+ /** File extension (lowercase, without dot) */
34
+ extension: string;
35
+ /** Whether this file type is typically web-compatible */
36
+ isWebCompatible: boolean;
37
+ }
38
+
39
+ /**
40
+ * Extended metadata for image files.
41
+ */
42
+ export interface ImageMetadata extends BaseMetadata {
43
+ mediaType: "image";
44
+ /** Width in pixels */
45
+ width?: number;
46
+ /** Height in pixels */
47
+ height?: number;
48
+ /** Aspect ratio (width / height) */
49
+ aspectRatio?: number;
50
+ /** Whether the format supports transparency */
51
+ supportsTransparency: boolean;
52
+ /** Whether this is a vector format (infinitely scalable) */
53
+ isVector: boolean;
54
+ /** Whether this format supports animation */
55
+ supportsAnimation: boolean;
56
+ /** Suggested image optimization format */
57
+ suggestedFormat?: "webp" | "avif" | "jpeg" | "png" | "svg";
58
+ }
59
+
60
+ /**
61
+ * Extended metadata for video files.
62
+ */
63
+ export interface VideoMetadata extends BaseMetadata {
64
+ mediaType: "video";
65
+ /** Width in pixels */
66
+ width?: number;
67
+ /** Height in pixels */
68
+ height?: number;
69
+ /** Aspect ratio (width / height) */
70
+ aspectRatio?: number;
71
+ /** Duration in seconds */
72
+ duration?: number;
73
+ /** Duration formatted as HH:MM:SS */
74
+ durationFormatted?: string;
75
+ /** Video codec (if known) */
76
+ codec?: string;
77
+ /** Whether the format is widely supported in browsers */
78
+ hasBroadBrowserSupport: boolean;
79
+ }
80
+
81
+ /**
82
+ * Extended metadata for audio files.
83
+ */
84
+ export interface AudioMetadata extends BaseMetadata {
85
+ mediaType: "audio";
86
+ /** Duration in seconds */
87
+ duration?: number;
88
+ /** Duration formatted as HH:MM:SS */
89
+ durationFormatted?: string;
90
+ /** Audio codec (if known) */
91
+ codec?: string;
92
+ /** Whether the format is widely supported in browsers */
93
+ hasBroadBrowserSupport: boolean;
94
+ }
95
+
96
+ /**
97
+ * Extended metadata for document files.
98
+ */
99
+ export interface DocumentMetadata extends BaseMetadata {
100
+ mediaType: "document";
101
+ /** Page count (if known, typically from PDF) */
102
+ pageCount?: number;
103
+ /** Document category */
104
+ documentCategory:
105
+ | "pdf"
106
+ | "word"
107
+ | "spreadsheet"
108
+ | "presentation"
109
+ | "text"
110
+ | "other";
111
+ /** Whether the document can be previewed in browser */
112
+ canPreviewInBrowser: boolean;
113
+ }
114
+
115
+ /**
116
+ * Metadata for unrecognized file types.
117
+ */
118
+ export interface OtherMetadata extends BaseMetadata {
119
+ mediaType: "other";
120
+ }
121
+
122
+ /**
123
+ * Union type for all possible metadata shapes.
124
+ */
125
+ export type ExtractedMetadata =
126
+ | ImageMetadata
127
+ | VideoMetadata
128
+ | AudioMetadata
129
+ | DocumentMetadata
130
+ | OtherMetadata;
131
+
132
+ /**
133
+ * Input parameters for metadata extraction.
134
+ */
135
+ export interface MetadataExtractionInput {
136
+ /** Original filename */
137
+ filename: string;
138
+ /** MIME type of the file */
139
+ mimeType: string;
140
+ /** File size in bytes */
141
+ size: number;
142
+ /** Optional width in pixels (for images/videos, from client) */
143
+ width?: number;
144
+ /** Optional height in pixels (for images/videos, from client) */
145
+ height?: number;
146
+ /** Optional duration in seconds (for audio/video, from client) */
147
+ duration?: number;
148
+ /** Optional page count (for documents, from client) */
149
+ pageCount?: number;
150
+ }
151
+
152
+ // =============================================================================
153
+ // MIME Type Mappings
154
+ // =============================================================================
155
+
156
+ /**
157
+ * MIME types classified as images.
158
+ */
159
+ const IMAGE_MIME_TYPES = new Set([
160
+ "image/jpeg",
161
+ "image/jpg",
162
+ "image/png",
163
+ "image/gif",
164
+ "image/webp",
165
+ "image/avif",
166
+ "image/svg+xml",
167
+ "image/bmp",
168
+ "image/tiff",
169
+ "image/x-icon",
170
+ "image/ico",
171
+ "image/heic",
172
+ "image/heif",
173
+ ]);
174
+
175
+ /**
176
+ * MIME types classified as videos.
177
+ */
178
+ const VIDEO_MIME_TYPES = new Set([
179
+ "video/mp4",
180
+ "video/webm",
181
+ "video/ogg",
182
+ "video/quicktime",
183
+ "video/x-msvideo",
184
+ "video/mpeg",
185
+ "video/3gpp",
186
+ "video/x-matroska",
187
+ "video/x-flv",
188
+ ]);
189
+
190
+ /**
191
+ * MIME types classified as audio.
192
+ */
193
+ const AUDIO_MIME_TYPES = new Set([
194
+ "audio/mpeg",
195
+ "audio/mp3",
196
+ "audio/ogg",
197
+ "audio/wav",
198
+ "audio/webm",
199
+ "audio/aac",
200
+ "audio/flac",
201
+ "audio/x-m4a",
202
+ "audio/mp4",
203
+ "audio/x-wav",
204
+ ]);
205
+
206
+ /**
207
+ * MIME types classified as documents.
208
+ */
209
+ const DOCUMENT_MIME_TYPES = new Set([
210
+ "application/pdf",
211
+ "application/msword",
212
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
213
+ "application/vnd.ms-excel",
214
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
215
+ "application/vnd.ms-powerpoint",
216
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
217
+ "text/plain",
218
+ "text/csv",
219
+ "text/markdown",
220
+ "application/rtf",
221
+ "application/json",
222
+ "application/xml",
223
+ "text/xml",
224
+ ]);
225
+
226
+ /**
227
+ * MIME types that support transparency.
228
+ */
229
+ const TRANSPARENT_MIME_TYPES = new Set([
230
+ "image/png",
231
+ "image/webp",
232
+ "image/gif",
233
+ "image/avif",
234
+ "image/svg+xml",
235
+ ]);
236
+
237
+ /**
238
+ * Vector image MIME types.
239
+ */
240
+ const VECTOR_MIME_TYPES = new Set(["image/svg+xml"]);
241
+
242
+ /**
243
+ * MIME types that support animation.
244
+ */
245
+ const ANIMATED_MIME_TYPES = new Set([
246
+ "image/gif",
247
+ "image/webp",
248
+ "image/avif",
249
+ "image/png", // APNG
250
+ ]);
251
+
252
+ /**
253
+ * Web-compatible image MIME types (work natively in all modern browsers).
254
+ */
255
+ const WEB_COMPATIBLE_IMAGE_TYPES = new Set([
256
+ "image/jpeg",
257
+ "image/jpg",
258
+ "image/png",
259
+ "image/gif",
260
+ "image/webp",
261
+ "image/svg+xml",
262
+ "image/avif",
263
+ ]);
264
+
265
+ /**
266
+ * Web-compatible video MIME types.
267
+ */
268
+ const WEB_COMPATIBLE_VIDEO_TYPES = new Set([
269
+ "video/mp4",
270
+ "video/webm",
271
+ "video/ogg",
272
+ ]);
273
+
274
+ /**
275
+ * Web-compatible audio MIME types.
276
+ */
277
+ const WEB_COMPATIBLE_AUDIO_TYPES = new Set([
278
+ "audio/mpeg",
279
+ "audio/mp3",
280
+ "audio/ogg",
281
+ "audio/wav",
282
+ "audio/webm",
283
+ "audio/aac",
284
+ ]);
285
+
286
+ // =============================================================================
287
+ // Helper Functions
288
+ // =============================================================================
289
+
290
+ /**
291
+ * Extracts the file extension from a filename.
292
+ *
293
+ * @param filename - The filename to extract the extension from
294
+ * @returns The lowercase extension without the dot, or empty string if none
295
+ */
296
+ export function extractExtension(filename: string): string {
297
+ const lastDot = filename.lastIndexOf(".");
298
+ if (lastDot === -1 || lastDot === filename.length - 1) {
299
+ return "";
300
+ }
301
+ return filename.slice(lastDot + 1).toLowerCase();
302
+ }
303
+
304
+ /**
305
+ * Classifies a MIME type into a media category.
306
+ *
307
+ * @param mimeType - The MIME type to classify
308
+ * @returns The media type category
309
+ */
310
+ export function classifyMimeType(
311
+ mimeType: string
312
+ ): "image" | "video" | "audio" | "document" | "other" {
313
+ const normalizedMime = mimeType.toLowerCase();
314
+
315
+ if (IMAGE_MIME_TYPES.has(normalizedMime)) {
316
+ return "image";
317
+ }
318
+ if (VIDEO_MIME_TYPES.has(normalizedMime)) {
319
+ return "video";
320
+ }
321
+ if (AUDIO_MIME_TYPES.has(normalizedMime)) {
322
+ return "audio";
323
+ }
324
+ if (DOCUMENT_MIME_TYPES.has(normalizedMime)) {
325
+ return "document";
326
+ }
327
+
328
+ // Fallback: check MIME type prefix
329
+ if (normalizedMime.startsWith("image/")) {
330
+ return "image";
331
+ }
332
+ if (normalizedMime.startsWith("video/")) {
333
+ return "video";
334
+ }
335
+ if (normalizedMime.startsWith("audio/")) {
336
+ return "audio";
337
+ }
338
+ if (
339
+ normalizedMime.startsWith("text/") ||
340
+ normalizedMime.startsWith("application/")
341
+ ) {
342
+ // Check for common document patterns
343
+ if (
344
+ normalizedMime.includes("document") ||
345
+ normalizedMime.includes("sheet") ||
346
+ normalizedMime.includes("presentation")
347
+ ) {
348
+ return "document";
349
+ }
350
+ }
351
+
352
+ return "other";
353
+ }
354
+
355
+ /**
356
+ * Calculates aspect ratio from dimensions.
357
+ *
358
+ * @param width - Width in pixels
359
+ * @param height - Height in pixels
360
+ * @returns Aspect ratio rounded to 3 decimal places, or undefined if invalid
361
+ */
362
+ export function calculateAspectRatio(
363
+ width?: number,
364
+ height?: number
365
+ ): number | undefined {
366
+ if (!width || !height || height <= 0 || width <= 0) {
367
+ return undefined;
368
+ }
369
+ return Math.round((width / height) * 1000) / 1000;
370
+ }
371
+
372
+ /**
373
+ * Formats duration in seconds to HH:MM:SS format.
374
+ *
375
+ * @param seconds - Duration in seconds
376
+ * @returns Formatted duration string, or undefined if invalid
377
+ */
378
+ export function formatDuration(seconds?: number): string | undefined {
379
+ if (seconds === undefined || seconds < 0 || !Number.isFinite(seconds)) {
380
+ return undefined;
381
+ }
382
+
383
+ const hours = Math.floor(seconds / 3600);
384
+ const minutes = Math.floor((seconds % 3600) / 60);
385
+ const secs = Math.floor(seconds % 60);
386
+
387
+ if (hours > 0) {
388
+ return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
389
+ }
390
+ return `${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
391
+ }
392
+
393
+ /**
394
+ * Determines the optimal image format for web delivery.
395
+ *
396
+ * @param mimeType - Original MIME type
397
+ * @param supportsTransparency - Whether transparency is needed
398
+ * @returns Suggested format for optimization
399
+ */
400
+ export function suggestImageFormat(
401
+ mimeType: string,
402
+ supportsTransparency: boolean
403
+ ): "webp" | "avif" | "jpeg" | "png" | "svg" | undefined {
404
+ const normalizedMime = mimeType.toLowerCase();
405
+
406
+ // SVG stays as SVG (vector)
407
+ if (normalizedMime === "image/svg+xml") {
408
+ return "svg";
409
+ }
410
+
411
+ // For photos/complex images, suggest modern formats
412
+ if (supportsTransparency) {
413
+ return "webp"; // WebP supports transparency and has broad support
414
+ }
415
+
416
+ // For photos without transparency, AVIF or WebP
417
+ if (normalizedMime === "image/jpeg" || normalizedMime === "image/jpg") {
418
+ return "webp"; // Better compression than JPEG
419
+ }
420
+
421
+ return "webp"; // Default to WebP for best compatibility
422
+ }
423
+
424
+ /**
425
+ * Categorizes document MIME types into subcategories.
426
+ *
427
+ * @param mimeType - The document MIME type
428
+ * @returns Document category
429
+ */
430
+ export function categorizeDocument(
431
+ mimeType: string
432
+ ): "pdf" | "word" | "spreadsheet" | "presentation" | "text" | "other" {
433
+ const normalizedMime = mimeType.toLowerCase();
434
+
435
+ if (normalizedMime === "application/pdf") {
436
+ return "pdf";
437
+ }
438
+
439
+ if (
440
+ normalizedMime === "application/msword" ||
441
+ normalizedMime.includes("wordprocessingml")
442
+ ) {
443
+ return "word";
444
+ }
445
+
446
+ if (
447
+ normalizedMime === "application/vnd.ms-excel" ||
448
+ normalizedMime.includes("spreadsheetml") ||
449
+ normalizedMime === "text/csv"
450
+ ) {
451
+ return "spreadsheet";
452
+ }
453
+
454
+ if (
455
+ normalizedMime === "application/vnd.ms-powerpoint" ||
456
+ normalizedMime.includes("presentationml")
457
+ ) {
458
+ return "presentation";
459
+ }
460
+
461
+ if (
462
+ normalizedMime.startsWith("text/") ||
463
+ normalizedMime === "application/json" ||
464
+ normalizedMime === "application/xml"
465
+ ) {
466
+ return "text";
467
+ }
468
+
469
+ return "other";
470
+ }
471
+
472
+ /**
473
+ * Determines if a document can be previewed in the browser.
474
+ *
475
+ * @param mimeType - The document MIME type
476
+ * @returns Whether the document can be previewed
477
+ */
478
+ export function canPreviewDocument(mimeType: string): boolean {
479
+ const normalizedMime = mimeType.toLowerCase();
480
+
481
+ // PDFs can be previewed in most browsers
482
+ if (normalizedMime === "application/pdf") {
483
+ return true;
484
+ }
485
+
486
+ // Text-based formats can be displayed
487
+ if (normalizedMime.startsWith("text/")) {
488
+ return true;
489
+ }
490
+
491
+ // JSON and XML can be displayed
492
+ if (
493
+ normalizedMime === "application/json" ||
494
+ normalizedMime === "application/xml" ||
495
+ normalizedMime === "text/xml"
496
+ ) {
497
+ return true;
498
+ }
499
+
500
+ return false;
501
+ }
502
+
503
+ // =============================================================================
504
+ // Main Extraction Function
505
+ // =============================================================================
506
+
507
+ /**
508
+ * Extracts and normalizes metadata from uploaded file information.
509
+ *
510
+ * This is the main function to call for metadata extraction. It analyzes
511
+ * the MIME type, filename, and any provided dimensions/duration to build
512
+ * a comprehensive metadata object specific to the file type.
513
+ *
514
+ * @param input - The file information to extract metadata from
515
+ * @returns Structured metadata object with type-specific properties
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * // Image with dimensions from browser
520
+ * const imageMetadata = extractMetadata({
521
+ * filename: "photo.jpg",
522
+ * mimeType: "image/jpeg",
523
+ * size: 500000,
524
+ * width: 1920,
525
+ * height: 1080,
526
+ * });
527
+ * // Returns ImageMetadata with aspectRatio, suggestedFormat, etc.
528
+ *
529
+ * // Video with duration from browser
530
+ * const videoMetadata = extractMetadata({
531
+ * filename: "clip.mp4",
532
+ * mimeType: "video/mp4",
533
+ * size: 10000000,
534
+ * width: 1920,
535
+ * height: 1080,
536
+ * duration: 120,
537
+ * });
538
+ * // Returns VideoMetadata with durationFormatted, hasBroadBrowserSupport, etc.
539
+ *
540
+ * // Document
541
+ * const docMetadata = extractMetadata({
542
+ * filename: "report.pdf",
543
+ * mimeType: "application/pdf",
544
+ * size: 2000000,
545
+ * pageCount: 15,
546
+ * });
547
+ * // Returns DocumentMetadata with documentCategory, canPreviewInBrowser, etc.
548
+ * ```
549
+ */
550
+ export function extractMetadata(input: MetadataExtractionInput): ExtractedMetadata {
551
+ const { filename, mimeType, size, width, height, duration, pageCount } = input;
552
+
553
+ const normalizedMime = mimeType.toLowerCase();
554
+ const extension = extractExtension(filename);
555
+ const mediaType = classifyMimeType(normalizedMime);
556
+
557
+ // Build base metadata
558
+ const base: BaseMetadata = {
559
+ filename,
560
+ mimeType: normalizedMime,
561
+ size,
562
+ mediaType,
563
+ extension,
564
+ isWebCompatible: false, // Will be set by type-specific logic
565
+ };
566
+
567
+ switch (mediaType) {
568
+ case "image":
569
+ return extractImageMetadata(base, normalizedMime, width, height);
570
+
571
+ case "video":
572
+ return extractVideoMetadata(base, normalizedMime, width, height, duration);
573
+
574
+ case "audio":
575
+ return extractAudioMetadata(base, normalizedMime, duration);
576
+
577
+ case "document":
578
+ return extractDocumentMetadata(base, normalizedMime, pageCount);
579
+
580
+ default:
581
+ return {
582
+ ...base,
583
+ mediaType: "other",
584
+ isWebCompatible: false,
585
+ };
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Extracts image-specific metadata.
591
+ */
592
+ function extractImageMetadata(
593
+ base: BaseMetadata,
594
+ mimeType: string,
595
+ width?: number,
596
+ height?: number
597
+ ): ImageMetadata {
598
+ const supportsTransparency = TRANSPARENT_MIME_TYPES.has(mimeType);
599
+ const isVector = VECTOR_MIME_TYPES.has(mimeType);
600
+ const supportsAnimation = ANIMATED_MIME_TYPES.has(mimeType);
601
+ const isWebCompatible = WEB_COMPATIBLE_IMAGE_TYPES.has(mimeType);
602
+
603
+ const metadata: ImageMetadata = {
604
+ ...base,
605
+ mediaType: "image",
606
+ isWebCompatible,
607
+ supportsTransparency,
608
+ isVector,
609
+ supportsAnimation,
610
+ };
611
+
612
+ if (width !== undefined && width > 0) {
613
+ metadata.width = width;
614
+ }
615
+ if (height !== undefined && height > 0) {
616
+ metadata.height = height;
617
+ }
618
+
619
+ const aspectRatio = calculateAspectRatio(width, height);
620
+ if (aspectRatio !== undefined) {
621
+ metadata.aspectRatio = aspectRatio;
622
+ }
623
+
624
+ const suggestedFormat = suggestImageFormat(mimeType, supportsTransparency);
625
+ if (suggestedFormat) {
626
+ metadata.suggestedFormat = suggestedFormat;
627
+ }
628
+
629
+ return metadata;
630
+ }
631
+
632
+ /**
633
+ * Extracts video-specific metadata.
634
+ */
635
+ function extractVideoMetadata(
636
+ base: BaseMetadata,
637
+ mimeType: string,
638
+ width?: number,
639
+ height?: number,
640
+ duration?: number
641
+ ): VideoMetadata {
642
+ const hasBroadBrowserSupport = WEB_COMPATIBLE_VIDEO_TYPES.has(mimeType);
643
+ const isWebCompatible = hasBroadBrowserSupport;
644
+
645
+ const metadata: VideoMetadata = {
646
+ ...base,
647
+ mediaType: "video",
648
+ isWebCompatible,
649
+ hasBroadBrowserSupport,
650
+ };
651
+
652
+ if (width !== undefined && width > 0) {
653
+ metadata.width = width;
654
+ }
655
+ if (height !== undefined && height > 0) {
656
+ metadata.height = height;
657
+ }
658
+
659
+ const aspectRatio = calculateAspectRatio(width, height);
660
+ if (aspectRatio !== undefined) {
661
+ metadata.aspectRatio = aspectRatio;
662
+ }
663
+
664
+ if (duration !== undefined && duration >= 0) {
665
+ metadata.duration = duration;
666
+ metadata.durationFormatted = formatDuration(duration);
667
+ }
668
+
669
+ // Infer codec from MIME type
670
+ if (mimeType === "video/mp4") {
671
+ metadata.codec = "H.264/AVC";
672
+ } else if (mimeType === "video/webm") {
673
+ metadata.codec = "VP8/VP9";
674
+ } else if (mimeType === "video/ogg") {
675
+ metadata.codec = "Theora";
676
+ }
677
+
678
+ return metadata;
679
+ }
680
+
681
+ /**
682
+ * Extracts audio-specific metadata.
683
+ */
684
+ function extractAudioMetadata(
685
+ base: BaseMetadata,
686
+ mimeType: string,
687
+ duration?: number
688
+ ): AudioMetadata {
689
+ const hasBroadBrowserSupport = WEB_COMPATIBLE_AUDIO_TYPES.has(mimeType);
690
+ const isWebCompatible = hasBroadBrowserSupport;
691
+
692
+ const metadata: AudioMetadata = {
693
+ ...base,
694
+ mediaType: "audio",
695
+ isWebCompatible,
696
+ hasBroadBrowserSupport,
697
+ };
698
+
699
+ if (duration !== undefined && duration >= 0) {
700
+ metadata.duration = duration;
701
+ metadata.durationFormatted = formatDuration(duration);
702
+ }
703
+
704
+ // Infer codec from MIME type
705
+ if (mimeType === "audio/mpeg" || mimeType === "audio/mp3") {
706
+ metadata.codec = "MP3";
707
+ } else if (mimeType === "audio/ogg") {
708
+ metadata.codec = "Vorbis";
709
+ } else if (mimeType === "audio/wav" || mimeType === "audio/x-wav") {
710
+ metadata.codec = "PCM";
711
+ } else if (mimeType === "audio/aac") {
712
+ metadata.codec = "AAC";
713
+ } else if (mimeType === "audio/flac") {
714
+ metadata.codec = "FLAC";
715
+ }
716
+
717
+ return metadata;
718
+ }
719
+
720
+ /**
721
+ * Extracts document-specific metadata.
722
+ */
723
+ function extractDocumentMetadata(
724
+ base: BaseMetadata,
725
+ mimeType: string,
726
+ pageCount?: number
727
+ ): DocumentMetadata {
728
+ const documentCategory = categorizeDocument(mimeType);
729
+ const canPreview = canPreviewDocument(mimeType);
730
+
731
+ const metadata: DocumentMetadata = {
732
+ ...base,
733
+ mediaType: "document",
734
+ isWebCompatible: canPreview,
735
+ documentCategory,
736
+ canPreviewInBrowser: canPreview,
737
+ };
738
+
739
+ if (pageCount !== undefined && pageCount > 0) {
740
+ metadata.pageCount = pageCount;
741
+ }
742
+
743
+ return metadata;
744
+ }
745
+
746
+ // =============================================================================
747
+ // Validation Helpers
748
+ // =============================================================================
749
+
750
+ /**
751
+ * Validates that dimensions are positive integers.
752
+ *
753
+ * @param width - Width to validate
754
+ * @param height - Height to validate
755
+ * @returns Whether both dimensions are valid
756
+ */
757
+ export function validateDimensions(width?: number, height?: number): boolean {
758
+ if (width !== undefined) {
759
+ if (!Number.isInteger(width) || width <= 0) {
760
+ return false;
761
+ }
762
+ }
763
+ if (height !== undefined) {
764
+ if (!Number.isInteger(height) || height <= 0) {
765
+ return false;
766
+ }
767
+ }
768
+ return true;
769
+ }
770
+
771
+ /**
772
+ * Validates that duration is a non-negative number.
773
+ *
774
+ * @param duration - Duration to validate
775
+ * @returns Whether the duration is valid
776
+ */
777
+ export function validateDuration(duration?: number): boolean {
778
+ if (duration === undefined) {
779
+ return true;
780
+ }
781
+ return Number.isFinite(duration) && duration >= 0;
782
+ }
783
+
784
+ /**
785
+ * Validates that file size is a positive integer.
786
+ *
787
+ * @param size - File size to validate
788
+ * @returns Whether the size is valid
789
+ */
790
+ export function validateFileSize(size: number): boolean {
791
+ return Number.isInteger(size) && size > 0;
792
+ }