@streetjs/storage 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +719 -0
  3. package/dist/access.d.ts +95 -0
  4. package/dist/access.d.ts.map +1 -0
  5. package/dist/access.js +114 -0
  6. package/dist/access.js.map +1 -0
  7. package/dist/cli/commands.d.ts +88 -0
  8. package/dist/cli/commands.d.ts.map +1 -0
  9. package/dist/cli/commands.js +369 -0
  10. package/dist/cli/commands.js.map +1 -0
  11. package/dist/cli/generators.d.ts +41 -0
  12. package/dist/cli/generators.d.ts.map +1 -0
  13. package/dist/cli/generators.js +119 -0
  14. package/dist/cli/generators.js.map +1 -0
  15. package/dist/directory.d.ts +88 -0
  16. package/dist/directory.d.ts.map +1 -0
  17. package/dist/directory.js +148 -0
  18. package/dist/directory.js.map +1 -0
  19. package/dist/driver.d.ts +83 -0
  20. package/dist/driver.d.ts.map +1 -0
  21. package/dist/driver.js +16 -0
  22. package/dist/driver.js.map +1 -0
  23. package/dist/drivers/azure.d.ts +239 -0
  24. package/dist/drivers/azure.d.ts.map +1 -0
  25. package/dist/drivers/azure.js +476 -0
  26. package/dist/drivers/azure.js.map +1 -0
  27. package/dist/drivers/backblaze.d.ts +105 -0
  28. package/dist/drivers/backblaze.d.ts.map +1 -0
  29. package/dist/drivers/backblaze.js +279 -0
  30. package/dist/drivers/backblaze.js.map +1 -0
  31. package/dist/drivers/gcs.d.ts +239 -0
  32. package/dist/drivers/gcs.d.ts.map +1 -0
  33. package/dist/drivers/gcs.js +454 -0
  34. package/dist/drivers/gcs.js.map +1 -0
  35. package/dist/drivers/local.d.ts +126 -0
  36. package/dist/drivers/local.d.ts.map +1 -0
  37. package/dist/drivers/local.js +321 -0
  38. package/dist/drivers/local.js.map +1 -0
  39. package/dist/drivers/memory.d.ts +104 -0
  40. package/dist/drivers/memory.d.ts.map +1 -0
  41. package/dist/drivers/memory.js +197 -0
  42. package/dist/drivers/memory.js.map +1 -0
  43. package/dist/drivers/minio.d.ts +90 -0
  44. package/dist/drivers/minio.d.ts.map +1 -0
  45. package/dist/drivers/minio.js +276 -0
  46. package/dist/drivers/minio.js.map +1 -0
  47. package/dist/drivers/r2.d.ts +100 -0
  48. package/dist/drivers/r2.d.ts.map +1 -0
  49. package/dist/drivers/r2.js +240 -0
  50. package/dist/drivers/r2.js.map +1 -0
  51. package/dist/drivers/s3-base.d.ts +270 -0
  52. package/dist/drivers/s3-base.d.ts.map +1 -0
  53. package/dist/drivers/s3-base.js +436 -0
  54. package/dist/drivers/s3-base.js.map +1 -0
  55. package/dist/drivers/s3.d.ts +111 -0
  56. package/dist/drivers/s3.d.ts.map +1 -0
  57. package/dist/drivers/s3.js +311 -0
  58. package/dist/drivers/s3.js.map +1 -0
  59. package/dist/drivers/supabase.d.ts +246 -0
  60. package/dist/drivers/supabase.d.ts.map +1 -0
  61. package/dist/drivers/supabase.js +502 -0
  62. package/dist/drivers/supabase.js.map +1 -0
  63. package/dist/errors.d.ts +115 -0
  64. package/dist/errors.d.ts.map +1 -0
  65. package/dist/errors.js +131 -0
  66. package/dist/errors.js.map +1 -0
  67. package/dist/examples/storage-demo.d.ts +53 -0
  68. package/dist/examples/storage-demo.d.ts.map +1 -0
  69. package/dist/examples/storage-demo.js +242 -0
  70. package/dist/examples/storage-demo.js.map +1 -0
  71. package/dist/facade.d.ts +192 -0
  72. package/dist/facade.d.ts.map +1 -0
  73. package/dist/facade.js +898 -0
  74. package/dist/facade.js.map +1 -0
  75. package/dist/image.d.ts +94 -0
  76. package/dist/image.d.ts.map +1 -0
  77. package/dist/image.js +224 -0
  78. package/dist/image.js.map +1 -0
  79. package/dist/index.d.ts +47 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +53 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/integrations/events.d.ts +108 -0
  84. package/dist/integrations/events.d.ts.map +1 -0
  85. package/dist/integrations/events.js +111 -0
  86. package/dist/integrations/events.js.map +1 -0
  87. package/dist/integrations/queue.d.ts +89 -0
  88. package/dist/integrations/queue.d.ts.map +1 -0
  89. package/dist/integrations/queue.js +91 -0
  90. package/dist/integrations/queue.js.map +1 -0
  91. package/dist/integrations/realtime.d.ts +95 -0
  92. package/dist/integrations/realtime.d.ts.map +1 -0
  93. package/dist/integrations/realtime.js +90 -0
  94. package/dist/integrations/realtime.js.map +1 -0
  95. package/dist/lifecycle.d.ts +137 -0
  96. package/dist/lifecycle.d.ts.map +1 -0
  97. package/dist/lifecycle.js +240 -0
  98. package/dist/lifecycle.js.map +1 -0
  99. package/dist/metadata.d.ts +109 -0
  100. package/dist/metadata.d.ts.map +1 -0
  101. package/dist/metadata.js +139 -0
  102. package/dist/metadata.js.map +1 -0
  103. package/dist/multipart.d.ts +108 -0
  104. package/dist/multipart.d.ts.map +1 -0
  105. package/dist/multipart.js +200 -0
  106. package/dist/multipart.js.map +1 -0
  107. package/dist/observability.d.ts +82 -0
  108. package/dist/observability.d.ts.map +1 -0
  109. package/dist/observability.js +175 -0
  110. package/dist/observability.js.map +1 -0
  111. package/dist/plugin.d.ts +82 -0
  112. package/dist/plugin.d.ts.map +1 -0
  113. package/dist/plugin.js +100 -0
  114. package/dist/plugin.js.map +1 -0
  115. package/dist/resumable.d.ts +118 -0
  116. package/dist/resumable.d.ts.map +1 -0
  117. package/dist/resumable.js +223 -0
  118. package/dist/resumable.js.map +1 -0
  119. package/dist/search.d.ts +57 -0
  120. package/dist/search.d.ts.map +1 -0
  121. package/dist/search.js +172 -0
  122. package/dist/search.js.map +1 -0
  123. package/dist/signed-url.d.ts +110 -0
  124. package/dist/signed-url.d.ts.map +1 -0
  125. package/dist/signed-url.js +237 -0
  126. package/dist/signed-url.js.map +1 -0
  127. package/dist/testing/index.d.ts +219 -0
  128. package/dist/testing/index.d.ts.map +1 -0
  129. package/dist/testing/index.js +393 -0
  130. package/dist/testing/index.js.map +1 -0
  131. package/dist/tests/access.test.d.ts +2 -0
  132. package/dist/tests/access.test.d.ts.map +1 -0
  133. package/dist/tests/access.test.js +270 -0
  134. package/dist/tests/access.test.js.map +1 -0
  135. package/dist/tests/azure-driver.test.d.ts +2 -0
  136. package/dist/tests/azure-driver.test.d.ts.map +1 -0
  137. package/dist/tests/azure-driver.test.js +183 -0
  138. package/dist/tests/azure-driver.test.js.map +1 -0
  139. package/dist/tests/backblaze-driver.test.d.ts +2 -0
  140. package/dist/tests/backblaze-driver.test.d.ts.map +1 -0
  141. package/dist/tests/backblaze-driver.test.js +185 -0
  142. package/dist/tests/backblaze-driver.test.js.map +1 -0
  143. package/dist/tests/bridge-degradation.test.d.ts +2 -0
  144. package/dist/tests/bridge-degradation.test.d.ts.map +1 -0
  145. package/dist/tests/bridge-degradation.test.js +138 -0
  146. package/dist/tests/bridge-degradation.test.js.map +1 -0
  147. package/dist/tests/cli.test.d.ts +2 -0
  148. package/dist/tests/cli.test.d.ts.map +1 -0
  149. package/dist/tests/cli.test.js +127 -0
  150. package/dist/tests/cli.test.js.map +1 -0
  151. package/dist/tests/contract.d.ts +131 -0
  152. package/dist/tests/contract.d.ts.map +1 -0
  153. package/dist/tests/contract.js +222 -0
  154. package/dist/tests/contract.js.map +1 -0
  155. package/dist/tests/contract.test.d.ts +2 -0
  156. package/dist/tests/contract.test.d.ts.map +1 -0
  157. package/dist/tests/contract.test.js +110 -0
  158. package/dist/tests/contract.test.js.map +1 -0
  159. package/dist/tests/directory.test.d.ts +2 -0
  160. package/dist/tests/directory.test.d.ts.map +1 -0
  161. package/dist/tests/directory.test.js +125 -0
  162. package/dist/tests/directory.test.js.map +1 -0
  163. package/dist/tests/errors.test.d.ts +2 -0
  164. package/dist/tests/errors.test.d.ts.map +1 -0
  165. package/dist/tests/errors.test.js +93 -0
  166. package/dist/tests/errors.test.js.map +1 -0
  167. package/dist/tests/events-bridge.test.d.ts +2 -0
  168. package/dist/tests/events-bridge.test.d.ts.map +1 -0
  169. package/dist/tests/events-bridge.test.js +228 -0
  170. package/dist/tests/events-bridge.test.js.map +1 -0
  171. package/dist/tests/example-smoke.test.d.ts +2 -0
  172. package/dist/tests/example-smoke.test.d.ts.map +1 -0
  173. package/dist/tests/example-smoke.test.js +34 -0
  174. package/dist/tests/example-smoke.test.js.map +1 -0
  175. package/dist/tests/gcs-driver.test.d.ts +2 -0
  176. package/dist/tests/gcs-driver.test.d.ts.map +1 -0
  177. package/dist/tests/gcs-driver.test.js +197 -0
  178. package/dist/tests/gcs-driver.test.js.map +1 -0
  179. package/dist/tests/image.test.d.ts +2 -0
  180. package/dist/tests/image.test.d.ts.map +1 -0
  181. package/dist/tests/image.test.js +102 -0
  182. package/dist/tests/image.test.js.map +1 -0
  183. package/dist/tests/lifecycle.test.d.ts +2 -0
  184. package/dist/tests/lifecycle.test.d.ts.map +1 -0
  185. package/dist/tests/lifecycle.test.js +171 -0
  186. package/dist/tests/lifecycle.test.js.map +1 -0
  187. package/dist/tests/local-driver.test.d.ts +2 -0
  188. package/dist/tests/local-driver.test.d.ts.map +1 -0
  189. package/dist/tests/local-driver.test.js +153 -0
  190. package/dist/tests/local-driver.test.js.map +1 -0
  191. package/dist/tests/local-streaming.test.d.ts +2 -0
  192. package/dist/tests/local-streaming.test.d.ts.map +1 -0
  193. package/dist/tests/local-streaming.test.js +121 -0
  194. package/dist/tests/local-streaming.test.js.map +1 -0
  195. package/dist/tests/memory-driver.test.d.ts +2 -0
  196. package/dist/tests/memory-driver.test.d.ts.map +1 -0
  197. package/dist/tests/memory-driver.test.js +180 -0
  198. package/dist/tests/memory-driver.test.js.map +1 -0
  199. package/dist/tests/memory-streaming.test.d.ts +2 -0
  200. package/dist/tests/memory-streaming.test.d.ts.map +1 -0
  201. package/dist/tests/memory-streaming.test.js +92 -0
  202. package/dist/tests/memory-streaming.test.js.map +1 -0
  203. package/dist/tests/minio-driver.test.d.ts +2 -0
  204. package/dist/tests/minio-driver.test.d.ts.map +1 -0
  205. package/dist/tests/minio-driver.test.js +146 -0
  206. package/dist/tests/minio-driver.test.js.map +1 -0
  207. package/dist/tests/non-s3-integration.test.d.ts +2 -0
  208. package/dist/tests/non-s3-integration.test.d.ts.map +1 -0
  209. package/dist/tests/non-s3-integration.test.js +201 -0
  210. package/dist/tests/non-s3-integration.test.js.map +1 -0
  211. package/dist/tests/observability.registration.test.d.ts +2 -0
  212. package/dist/tests/observability.registration.test.d.ts.map +1 -0
  213. package/dist/tests/observability.registration.test.js +103 -0
  214. package/dist/tests/observability.registration.test.js.map +1 -0
  215. package/dist/tests/observability.test.d.ts +2 -0
  216. package/dist/tests/observability.test.d.ts.map +1 -0
  217. package/dist/tests/observability.test.js +240 -0
  218. package/dist/tests/observability.test.js.map +1 -0
  219. package/dist/tests/plugin.test.d.ts +2 -0
  220. package/dist/tests/plugin.test.d.ts.map +1 -0
  221. package/dist/tests/plugin.test.js +112 -0
  222. package/dist/tests/plugin.test.js.map +1 -0
  223. package/dist/tests/prop-concurrent.test.d.ts +2 -0
  224. package/dist/tests/prop-concurrent.test.d.ts.map +1 -0
  225. package/dist/tests/prop-concurrent.test.js +59 -0
  226. package/dist/tests/prop-concurrent.test.js.map +1 -0
  227. package/dist/tests/prop-copy.test.d.ts +2 -0
  228. package/dist/tests/prop-copy.test.d.ts.map +1 -0
  229. package/dist/tests/prop-copy.test.js +63 -0
  230. package/dist/tests/prop-copy.test.js.map +1 -0
  231. package/dist/tests/prop-delete.test.d.ts +2 -0
  232. package/dist/tests/prop-delete.test.d.ts.map +1 -0
  233. package/dist/tests/prop-delete.test.js +35 -0
  234. package/dist/tests/prop-delete.test.js.map +1 -0
  235. package/dist/tests/prop-directory.test.d.ts +2 -0
  236. package/dist/tests/prop-directory.test.d.ts.map +1 -0
  237. package/dist/tests/prop-directory.test.js +118 -0
  238. package/dist/tests/prop-directory.test.js.map +1 -0
  239. package/dist/tests/prop-driver-conformance.test.d.ts +2 -0
  240. package/dist/tests/prop-driver-conformance.test.d.ts.map +1 -0
  241. package/dist/tests/prop-driver-conformance.test.js +110 -0
  242. package/dist/tests/prop-driver-conformance.test.js.map +1 -0
  243. package/dist/tests/prop-driver-equivalence.test.d.ts +2 -0
  244. package/dist/tests/prop-driver-equivalence.test.d.ts.map +1 -0
  245. package/dist/tests/prop-driver-equivalence.test.js +163 -0
  246. package/dist/tests/prop-driver-equivalence.test.js.map +1 -0
  247. package/dist/tests/prop-lifecycle.test.d.ts +2 -0
  248. package/dist/tests/prop-lifecycle.test.d.ts.map +1 -0
  249. package/dist/tests/prop-lifecycle.test.js +83 -0
  250. package/dist/tests/prop-lifecycle.test.js.map +1 -0
  251. package/dist/tests/prop-list.test.d.ts +2 -0
  252. package/dist/tests/prop-list.test.d.ts.map +1 -0
  253. package/dist/tests/prop-list.test.js +53 -0
  254. package/dist/tests/prop-list.test.js.map +1 -0
  255. package/dist/tests/prop-metadata.test.d.ts +2 -0
  256. package/dist/tests/prop-metadata.test.d.ts.map +1 -0
  257. package/dist/tests/prop-metadata.test.js +87 -0
  258. package/dist/tests/prop-metadata.test.js.map +1 -0
  259. package/dist/tests/prop-move.test.d.ts +2 -0
  260. package/dist/tests/prop-move.test.d.ts.map +1 -0
  261. package/dist/tests/prop-move.test.js +67 -0
  262. package/dist/tests/prop-move.test.js.map +1 -0
  263. package/dist/tests/prop-multipart-abort.test.d.ts +2 -0
  264. package/dist/tests/prop-multipart-abort.test.d.ts.map +1 -0
  265. package/dist/tests/prop-multipart-abort.test.js +48 -0
  266. package/dist/tests/prop-multipart-abort.test.js.map +1 -0
  267. package/dist/tests/prop-multipart.test.d.ts +2 -0
  268. package/dist/tests/prop-multipart.test.d.ts.map +1 -0
  269. package/dist/tests/prop-multipart.test.js +65 -0
  270. package/dist/tests/prop-multipart.test.js.map +1 -0
  271. package/dist/tests/prop-resumable.test.d.ts +2 -0
  272. package/dist/tests/prop-resumable.test.d.ts.map +1 -0
  273. package/dist/tests/prop-resumable.test.js +78 -0
  274. package/dist/tests/prop-resumable.test.js.map +1 -0
  275. package/dist/tests/prop-search.test.d.ts +2 -0
  276. package/dist/tests/prop-search.test.d.ts.map +1 -0
  277. package/dist/tests/prop-search.test.js +120 -0
  278. package/dist/tests/prop-search.test.js.map +1 -0
  279. package/dist/tests/prop-signed-url.test.d.ts +2 -0
  280. package/dist/tests/prop-signed-url.test.d.ts.map +1 -0
  281. package/dist/tests/prop-signed-url.test.js +74 -0
  282. package/dist/tests/prop-signed-url.test.js.map +1 -0
  283. package/dist/tests/prop-streaming.test.d.ts +2 -0
  284. package/dist/tests/prop-streaming.test.d.ts.map +1 -0
  285. package/dist/tests/prop-streaming.test.js +34 -0
  286. package/dist/tests/prop-streaming.test.js.map +1 -0
  287. package/dist/tests/prop-upload-download.test.d.ts +2 -0
  288. package/dist/tests/prop-upload-download.test.d.ts.map +1 -0
  289. package/dist/tests/prop-upload-download.test.js +30 -0
  290. package/dist/tests/prop-upload-download.test.js.map +1 -0
  291. package/dist/tests/prop-validation.test.d.ts +2 -0
  292. package/dist/tests/prop-validation.test.d.ts.map +1 -0
  293. package/dist/tests/prop-validation.test.js +50 -0
  294. package/dist/tests/prop-validation.test.js.map +1 -0
  295. package/dist/tests/prop-versioning.test.d.ts +2 -0
  296. package/dist/tests/prop-versioning.test.d.ts.map +1 -0
  297. package/dist/tests/prop-versioning.test.js +74 -0
  298. package/dist/tests/prop-versioning.test.js.map +1 -0
  299. package/dist/tests/queue-bridge.test.d.ts +2 -0
  300. package/dist/tests/queue-bridge.test.d.ts.map +1 -0
  301. package/dist/tests/queue-bridge.test.js +111 -0
  302. package/dist/tests/queue-bridge.test.js.map +1 -0
  303. package/dist/tests/r2-driver.test.d.ts +2 -0
  304. package/dist/tests/r2-driver.test.d.ts.map +1 -0
  305. package/dist/tests/r2-driver.test.js +143 -0
  306. package/dist/tests/r2-driver.test.js.map +1 -0
  307. package/dist/tests/realtime-bridge.test.d.ts +2 -0
  308. package/dist/tests/realtime-bridge.test.d.ts.map +1 -0
  309. package/dist/tests/realtime-bridge.test.js +106 -0
  310. package/dist/tests/realtime-bridge.test.js.map +1 -0
  311. package/dist/tests/resumable.test.d.ts +2 -0
  312. package/dist/tests/resumable.test.d.ts.map +1 -0
  313. package/dist/tests/resumable.test.js +104 -0
  314. package/dist/tests/resumable.test.js.map +1 -0
  315. package/dist/tests/s3-base.test.d.ts +2 -0
  316. package/dist/tests/s3-base.test.d.ts.map +1 -0
  317. package/dist/tests/s3-base.test.js +289 -0
  318. package/dist/tests/s3-base.test.js.map +1 -0
  319. package/dist/tests/s3-integration.test.d.ts +2 -0
  320. package/dist/tests/s3-integration.test.d.ts.map +1 -0
  321. package/dist/tests/s3-integration.test.js +270 -0
  322. package/dist/tests/s3-integration.test.js.map +1 -0
  323. package/dist/tests/s3.test.d.ts +2 -0
  324. package/dist/tests/s3.test.d.ts.map +1 -0
  325. package/dist/tests/s3.test.js +203 -0
  326. package/dist/tests/s3.test.js.map +1 -0
  327. package/dist/tests/scaffold.test.d.ts +2 -0
  328. package/dist/tests/scaffold.test.d.ts.map +1 -0
  329. package/dist/tests/scaffold.test.js +13 -0
  330. package/dist/tests/scaffold.test.js.map +1 -0
  331. package/dist/tests/search.test.d.ts +2 -0
  332. package/dist/tests/search.test.d.ts.map +1 -0
  333. package/dist/tests/search.test.js +106 -0
  334. package/dist/tests/search.test.js.map +1 -0
  335. package/dist/tests/supabase-driver.test.d.ts +2 -0
  336. package/dist/tests/supabase-driver.test.d.ts.map +1 -0
  337. package/dist/tests/supabase-driver.test.js +196 -0
  338. package/dist/tests/supabase-driver.test.js.map +1 -0
  339. package/dist/tests/testing-utilities.test.d.ts +2 -0
  340. package/dist/tests/testing-utilities.test.d.ts.map +1 -0
  341. package/dist/tests/testing-utilities.test.js +138 -0
  342. package/dist/tests/testing-utilities.test.js.map +1 -0
  343. package/dist/tests/validation.test.d.ts +2 -0
  344. package/dist/tests/validation.test.d.ts.map +1 -0
  345. package/dist/tests/validation.test.js +135 -0
  346. package/dist/tests/validation.test.js.map +1 -0
  347. package/dist/tests/versioning.test.d.ts +2 -0
  348. package/dist/tests/versioning.test.d.ts.map +1 -0
  349. package/dist/tests/versioning.test.js +128 -0
  350. package/dist/tests/versioning.test.js.map +1 -0
  351. package/dist/types.d.ts +232 -0
  352. package/dist/types.d.ts.map +1 -0
  353. package/dist/types.js +23 -0
  354. package/dist/types.js.map +1 -0
  355. package/dist/validation.d.ts +105 -0
  356. package/dist/validation.d.ts.map +1 -0
  357. package/dist/validation.js +225 -0
  358. package/dist/validation.js.map +1 -0
  359. package/dist/versioning.d.ts +100 -0
  360. package/dist/versioning.d.ts.map +1 -0
  361. package/dist/versioning.js +179 -0
  362. package/dist/versioning.js.map +1 -0
  363. package/package.json +137 -0
package/dist/search.js ADDED
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @streetjs/storage — attribute search / filtering over the flat key space
3
+ * (task 18.1).
4
+ *
5
+ * Object stores expose only a flat key space plus per-object metadata, so
6
+ * search is implemented as a provider-agnostic filter over the two mandatory
7
+ * driver primitives {@link StorageDriver.list} and {@link StorageDriver.stat}:
8
+ *
9
+ * 1. Enumerate candidate keys with `driver.list(prefix)` (the prefix filter is
10
+ * pushed down to the driver; an absent prefix lists the whole key space).
11
+ * 2. For each candidate, read its typed metadata with `driver.stat(key)`.
12
+ * 3. Keep the object only when it satisfies **every** supplied filter
13
+ * (conjunctive / AND semantics — Requirement 16.2); when no object satisfies
14
+ * the filters the result is the empty set (Requirement 16.3).
15
+ *
16
+ * The supported filters mirror {@link SearchFilters} (Requirement 16.1):
17
+ *
18
+ * - `prefix` — key begins with the prefix (pushed to `driver.list`).
19
+ * - `contentType` — exact content-type match.
20
+ * - `owner` — exact owner match.
21
+ * - `tenant` — exact tenant match.
22
+ * - `minSize` — size ≥ minSize (inclusive lower bound).
23
+ * - `maxSize` — size ≤ maxSize (inclusive upper bound).
24
+ * - `updatedAfter` — updatedAt ≥ updatedAfter (inclusive lower time bound).
25
+ * - `updatedBefore` — updatedAt ≤ updatedBefore (inclusive upper time bound).
26
+ * - `metadata` — every supplied custom field deep-equals the object's
27
+ * corresponding `custom` field.
28
+ *
29
+ * Reserved internal key spaces used by the cross-cutting layers
30
+ * (`.versions/`, `.multipart/`, `.resumable/`, `.archive/`) are excluded from
31
+ * results unless the supplied `prefix` explicitly targets one of them, so a
32
+ * broad search returns only user-visible objects and never leaks framework
33
+ * bookkeeping keys.
34
+ *
35
+ * The result preserves the {@link StorageListItem} shape returned by
36
+ * `driver.list`, so `search` composes with the rest of the facade surface.
37
+ *
38
+ * _Requirements: 16.1, 16.2, 16.3_
39
+ */
40
+ /**
41
+ * Reserved key prefixes that hold framework bookkeeping (version snapshots,
42
+ * multipart parts, resumable session bytes, archived copies). These are hidden
43
+ * from search results unless the caller explicitly searches within one of them.
44
+ */
45
+ const RESERVED_KEY_PREFIXES = [
46
+ ".versions/",
47
+ ".multipart/",
48
+ ".resumable/",
49
+ ".archive/",
50
+ ];
51
+ /**
52
+ * Return the stored objects that satisfy **every** supplied filter, evaluated
53
+ * over `driver.list` + `driver.stat`.
54
+ *
55
+ * The prefix filter is delegated to `driver.list`; the remaining filters are
56
+ * applied against each candidate's metadata. Objects whose metadata cannot be
57
+ * read (e.g. removed concurrently between `list` and `stat`) are skipped. When
58
+ * no object satisfies the filters, an empty array is returned (Requirement
59
+ * 16.3).
60
+ *
61
+ * @param driver The storage driver to search over.
62
+ * @param filters The conjunctive set of {@link SearchFilters} to apply.
63
+ * @returns The matching objects as {@link StorageListItem}s.
64
+ */
65
+ export async function searchObjects(driver, filters = {}) {
66
+ const prefix = filters.prefix ?? "";
67
+ const candidates = await driver.list(prefix);
68
+ const matches = [];
69
+ for (const item of candidates) {
70
+ // Defensive re-filter: never surface keys outside the requested prefix even
71
+ // if a driver interprets `list` loosely.
72
+ if (prefix !== "" && !item.key.startsWith(prefix)) {
73
+ continue;
74
+ }
75
+ // Hide reserved bookkeeping keys unless the search explicitly targets them.
76
+ if (isReservedKey(item.key) && !prefixTargetsReserved(prefix)) {
77
+ continue;
78
+ }
79
+ const metadata = await driver.stat(item.key);
80
+ if (metadata === null) {
81
+ // Object vanished between list and stat, or has no metadata — not a match.
82
+ continue;
83
+ }
84
+ if (matchesFilters(metadata, filters)) {
85
+ matches.push(item);
86
+ }
87
+ }
88
+ return matches;
89
+ }
90
+ /** Whether `key` lives in one of the reserved internal key spaces. */
91
+ function isReservedKey(key) {
92
+ return RESERVED_KEY_PREFIXES.some((reserved) => key.startsWith(reserved));
93
+ }
94
+ /** Whether the requested `prefix` explicitly targets a reserved key space. */
95
+ function prefixTargetsReserved(prefix) {
96
+ return RESERVED_KEY_PREFIXES.some((reserved) => prefix.startsWith(reserved));
97
+ }
98
+ /**
99
+ * Return true only when `metadata` satisfies every supplied filter (AND
100
+ * semantics). Absent filter fields impose no constraint.
101
+ */
102
+ function matchesFilters(metadata, filters) {
103
+ if (filters.contentType !== undefined && metadata.contentType !== filters.contentType) {
104
+ return false;
105
+ }
106
+ if (filters.owner !== undefined && metadata.owner !== filters.owner) {
107
+ return false;
108
+ }
109
+ if (filters.tenant !== undefined && metadata.tenant !== filters.tenant) {
110
+ return false;
111
+ }
112
+ if (filters.minSize !== undefined && metadata.size < filters.minSize) {
113
+ return false;
114
+ }
115
+ if (filters.maxSize !== undefined && metadata.size > filters.maxSize) {
116
+ return false;
117
+ }
118
+ if (filters.updatedAfter !== undefined && metadata.updatedAt < filters.updatedAfter) {
119
+ return false;
120
+ }
121
+ if (filters.updatedBefore !== undefined && metadata.updatedAt > filters.updatedBefore) {
122
+ return false;
123
+ }
124
+ if (filters.metadata !== undefined && !matchesCustomMetadata(metadata.custom, filters.metadata)) {
125
+ return false;
126
+ }
127
+ return true;
128
+ }
129
+ /**
130
+ * Return true when every field required by `required` is present in the
131
+ * object's `custom` metadata and deep-equals the required value.
132
+ */
133
+ function matchesCustomMetadata(custom, required) {
134
+ if (custom === null || typeof custom !== "object") {
135
+ return Object.keys(required).length === 0;
136
+ }
137
+ const source = custom;
138
+ for (const [field, value] of Object.entries(required)) {
139
+ if (!deepEqual(source[field], value)) {
140
+ return false;
141
+ }
142
+ }
143
+ return true;
144
+ }
145
+ /**
146
+ * Structural deep-equality used to compare custom metadata values. Handles
147
+ * primitives, arrays, and plain objects so nested custom fields compare by
148
+ * value rather than reference.
149
+ */
150
+ function deepEqual(a, b) {
151
+ if (Object.is(a, b)) {
152
+ return true;
153
+ }
154
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
155
+ return false;
156
+ }
157
+ if (Array.isArray(a) || Array.isArray(b)) {
158
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
159
+ return false;
160
+ }
161
+ return a.every((element, index) => deepEqual(element, b[index]));
162
+ }
163
+ const aObj = a;
164
+ const bObj = b;
165
+ const aKeys = Object.keys(aObj);
166
+ const bKeys = Object.keys(bObj);
167
+ if (aKeys.length !== bKeys.length) {
168
+ return false;
169
+ }
170
+ return aKeys.every((key) => Object.prototype.hasOwnProperty.call(bObj, key) && deepEqual(aObj[key], bObj[key]));
171
+ }
172
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @streetjs/storage — the signed URL service.
3
+ *
4
+ * A {@link Signed_URL} authorizes a single object operation (GET, PUT, or
5
+ * DELETE) on a single key until an expiry instant, letting clients act directly
6
+ * against storage without proxying through the application server
7
+ * (Requirement 8). {@link SignedUrlService} provides this capability in a
8
+ * **provider-agnostic** way:
9
+ *
10
+ * - When the backing {@link StorageDriver} implements the optional native
11
+ * `signedUrl` capability, the service delegates minting and verification to
12
+ * the driver so a provider with first-class signed-URL support (e.g. S3) is
13
+ * used directly.
14
+ * - Otherwise the service **simulates** signed URLs with an HMAC computed over
15
+ * the `(key, op, expiry)` tuple (plus the accepted options) using
16
+ * `config.signingSecret` (`node:crypto` HMAC-SHA256), mirroring the zero-
17
+ * dependency scheme described in the design. Verification recomputes the HMAC
18
+ * and checks the expiry against an injected {@link Clock} (default
19
+ * `systemClock`), so time is deterministic in tests (Requirement 8.3, 8.4).
20
+ *
21
+ * Semantics (Requirement 8):
22
+ * - {@link SignedUrlService.sign} mints a URL authorizing **exactly** the
23
+ * requested operation on the given key (Requirement 8.1) and accepts options
24
+ * for expiration, request headers, content type, maximum size, and custom
25
+ * metadata (Requirement 8.2).
26
+ * - {@link SignedUrlService.verify} returns a {@link SignedUrlVerification}
27
+ * whose `valid` flag is `true` only when the URL is well-formed, its signature
28
+ * matches, its authorized operation matches the operation being attempted, and
29
+ * it is used strictly before its expiry (Requirements 8.3, 8.4). Otherwise
30
+ * `valid` is `false` with a `reason` of `'expired'`, `'operation-mismatch'`,
31
+ * `'signature-mismatch'`, or `'malformed'`.
32
+ *
33
+ * The module depends only on the driver contract, the shared type surface, the
34
+ * error hierarchy, and `node:crypto`, keeping the dependency direction acyclic.
35
+ *
36
+ * _Requirements: 8.1, 8.2, 8.3, 8.4_
37
+ */
38
+ import { type Clock } from "streetjs";
39
+ import type { StorageDriver } from "./driver.js";
40
+ import type { SignedOperation, SignedUrlOptions, SignedUrlVerification } from "./types.js";
41
+ /** Options for constructing a {@link SignedUrlService}. */
42
+ export interface SignedUrlServiceOptions {
43
+ /**
44
+ * The HMAC key used to sign and verify simulated URLs. Required for the
45
+ * simulated path; minting throws a {@link StorageConfigError} when it is
46
+ * absent and the driver has no native `signedUrl` capability.
47
+ */
48
+ readonly signingSecret?: string;
49
+ /** Injected clock for deterministic expiry checks in tests. Default `systemClock`. */
50
+ readonly clock?: Clock;
51
+ /**
52
+ * The backing driver. When it exposes a native `signedUrl` capability, minting
53
+ * and verification delegate to it instead of using the HMAC simulation.
54
+ */
55
+ readonly driver?: StorageDriver;
56
+ }
57
+ /**
58
+ * Provider-agnostic signed URL service built on the driver contract and an HMAC
59
+ * simulation.
60
+ *
61
+ * A single instance is held by the facade and bound to one {@link StorageDriver}
62
+ * for its lifetime, so the native-vs-simulated decision (based on whether the
63
+ * driver exposes a `signedUrl` capability) is stable across every call.
64
+ */
65
+ export declare class SignedUrlService {
66
+ /** The HMAC signing secret for the simulated path, when configured. */
67
+ private readonly signingSecret?;
68
+ /** Injected clock used to stamp and check expiry. */
69
+ private readonly clock;
70
+ /** The driver's native signed-URL capability, when present. */
71
+ private readonly native?;
72
+ constructor(options?: SignedUrlServiceOptions);
73
+ /**
74
+ * Mint a URL authorizing **exactly** the operation `op` on `key`
75
+ * (Requirement 8.1). The URL carries the accepted options — expiration,
76
+ * request headers, content type, maximum size, and custom metadata
77
+ * (Requirement 8.2) — and, for the simulated path, an HMAC over the whole
78
+ * payload so its `(key, op, expiry)` tuple and options are tamper-evident.
79
+ *
80
+ * The expiry instant is computed from the injected clock plus
81
+ * `options.expiresInMs` (default {@link DEFAULT_EXPIRES_IN_MS}).
82
+ *
83
+ * @throws StorageConfigError when no native capability is available and no
84
+ * `signingSecret` was configured, since a URL cannot be signed without a key.
85
+ */
86
+ sign(key: string, op: SignedOperation, options?: SignedUrlOptions): Promise<string>;
87
+ /**
88
+ * Verify a signed `url` for the operation being attempted, returning a
89
+ * {@link SignedUrlVerification}.
90
+ *
91
+ * `valid` is `true` only when the URL is well-formed, its signature matches,
92
+ * its authorized operation equals `expectedOp` (when supplied), and it is used
93
+ * strictly before its expiry relative to `now` (Requirements 8.3, 8.4). When
94
+ * `expectedOp` is omitted the operation check is skipped and only signature and
95
+ * expiry are enforced. `now` defaults to the injected clock so tests can drive
96
+ * expiry deterministically.
97
+ *
98
+ * Failure reasons are reported precisely: `'malformed'` for an unparseable
99
+ * URL, `'signature-mismatch'` for a tampered/incorrectly signed URL,
100
+ * `'operation-mismatch'` when the authorized op differs from `expectedOp`, and
101
+ * `'expired'` when `now` is at or after the expiry.
102
+ */
103
+ verify(url: string, expectedOp?: SignedOperation, now?: number): SignedUrlVerification;
104
+ /**
105
+ * Resolve the signing secret or throw a descriptive {@link StorageConfigError}.
106
+ * Used only on the simulated minting path, where a secret is mandatory.
107
+ */
108
+ private requireSigningSecret;
109
+ }
110
+ //# sourceMappingURL=signed-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signed-url.d.ts","sourceRoot":"","sources":["../src/signed-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAKH,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAEnD,OAAO,KAAK,EAAuB,aAAa,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAoB3F,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AA2BD;;;;;;;GAOG;AACH,qBAAa,gBAAgB;IAC3B,uEAAuE;IACvE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAS;IAExC,qDAAqD;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAsB;gBAElC,OAAO,GAAE,uBAA4B;IAMjD;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;IAyB7F;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,qBAAqB;IA6CtF;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAS7B"}
@@ -0,0 +1,237 @@
1
+ /**
2
+ * @streetjs/storage — the signed URL service.
3
+ *
4
+ * A {@link Signed_URL} authorizes a single object operation (GET, PUT, or
5
+ * DELETE) on a single key until an expiry instant, letting clients act directly
6
+ * against storage without proxying through the application server
7
+ * (Requirement 8). {@link SignedUrlService} provides this capability in a
8
+ * **provider-agnostic** way:
9
+ *
10
+ * - When the backing {@link StorageDriver} implements the optional native
11
+ * `signedUrl` capability, the service delegates minting and verification to
12
+ * the driver so a provider with first-class signed-URL support (e.g. S3) is
13
+ * used directly.
14
+ * - Otherwise the service **simulates** signed URLs with an HMAC computed over
15
+ * the `(key, op, expiry)` tuple (plus the accepted options) using
16
+ * `config.signingSecret` (`node:crypto` HMAC-SHA256), mirroring the zero-
17
+ * dependency scheme described in the design. Verification recomputes the HMAC
18
+ * and checks the expiry against an injected {@link Clock} (default
19
+ * `systemClock`), so time is deterministic in tests (Requirement 8.3, 8.4).
20
+ *
21
+ * Semantics (Requirement 8):
22
+ * - {@link SignedUrlService.sign} mints a URL authorizing **exactly** the
23
+ * requested operation on the given key (Requirement 8.1) and accepts options
24
+ * for expiration, request headers, content type, maximum size, and custom
25
+ * metadata (Requirement 8.2).
26
+ * - {@link SignedUrlService.verify} returns a {@link SignedUrlVerification}
27
+ * whose `valid` flag is `true` only when the URL is well-formed, its signature
28
+ * matches, its authorized operation matches the operation being attempted, and
29
+ * it is used strictly before its expiry (Requirements 8.3, 8.4). Otherwise
30
+ * `valid` is `false` with a `reason` of `'expired'`, `'operation-mismatch'`,
31
+ * `'signature-mismatch'`, or `'malformed'`.
32
+ *
33
+ * The module depends only on the driver contract, the shared type surface, the
34
+ * error hierarchy, and `node:crypto`, keeping the dependency direction acyclic.
35
+ *
36
+ * _Requirements: 8.1, 8.2, 8.3, 8.4_
37
+ */
38
+ import { Buffer } from "node:buffer";
39
+ import { createHmac, timingSafeEqual } from "node:crypto";
40
+ import { systemClock } from "streetjs";
41
+ import { StorageConfigError } from "./errors.js";
42
+ /**
43
+ * The scheme prefix of a simulated signed URL. The remainder of the URL is
44
+ * `<base64url(payload)>.<hmacHex>`, where neither component can contain a `.`
45
+ * (base64url has no `.`; a hex digest has no `.`), so the last `.` is an
46
+ * unambiguous separator.
47
+ */
48
+ const SIGNED_URL_SCHEME = "streetjs-signed://";
49
+ /** The HMAC digest algorithm used to sign simulated URLs. */
50
+ const HMAC_ALGORITHM = "sha256";
51
+ /**
52
+ * The default validity window for a minted URL when `options.expiresInMs` is
53
+ * omitted (15 minutes). Chosen to be short-lived by default so an accidentally
54
+ * leaked URL is only briefly usable.
55
+ */
56
+ const DEFAULT_EXPIRES_IN_MS = 15 * 60 * 1000;
57
+ /**
58
+ * Provider-agnostic signed URL service built on the driver contract and an HMAC
59
+ * simulation.
60
+ *
61
+ * A single instance is held by the facade and bound to one {@link StorageDriver}
62
+ * for its lifetime, so the native-vs-simulated decision (based on whether the
63
+ * driver exposes a `signedUrl` capability) is stable across every call.
64
+ */
65
+ export class SignedUrlService {
66
+ /** The HMAC signing secret for the simulated path, when configured. */
67
+ signingSecret;
68
+ /** Injected clock used to stamp and check expiry. */
69
+ clock;
70
+ /** The driver's native signed-URL capability, when present. */
71
+ native;
72
+ constructor(options = {}) {
73
+ this.signingSecret = options.signingSecret;
74
+ this.clock = options.clock ?? systemClock;
75
+ this.native = options.driver?.signedUrl;
76
+ }
77
+ /**
78
+ * Mint a URL authorizing **exactly** the operation `op` on `key`
79
+ * (Requirement 8.1). The URL carries the accepted options — expiration,
80
+ * request headers, content type, maximum size, and custom metadata
81
+ * (Requirement 8.2) — and, for the simulated path, an HMAC over the whole
82
+ * payload so its `(key, op, expiry)` tuple and options are tamper-evident.
83
+ *
84
+ * The expiry instant is computed from the injected clock plus
85
+ * `options.expiresInMs` (default {@link DEFAULT_EXPIRES_IN_MS}).
86
+ *
87
+ * @throws StorageConfigError when no native capability is available and no
88
+ * `signingSecret` was configured, since a URL cannot be signed without a key.
89
+ */
90
+ async sign(key, op, options = {}) {
91
+ if (this.native !== undefined) {
92
+ return this.native.sign(key, op, options);
93
+ }
94
+ const secret = this.requireSigningSecret();
95
+ const expiresInMs = options.expiresInMs ?? DEFAULT_EXPIRES_IN_MS;
96
+ const expiry = this.clock() + expiresInMs;
97
+ const payload = {
98
+ v: 1,
99
+ key,
100
+ op,
101
+ expiry,
102
+ ...(options.headers !== undefined ? { headers: options.headers } : {}),
103
+ ...(options.contentType !== undefined ? { contentType: options.contentType } : {}),
104
+ ...(options.maxSize !== undefined ? { maxSize: options.maxSize } : {}),
105
+ ...(options.metadata !== undefined ? { metadata: options.metadata } : {}),
106
+ };
107
+ const encoded = encodePayload(payload);
108
+ const signature = hmacHex(secret, encoded);
109
+ return `${SIGNED_URL_SCHEME}${encoded}.${signature}`;
110
+ }
111
+ /**
112
+ * Verify a signed `url` for the operation being attempted, returning a
113
+ * {@link SignedUrlVerification}.
114
+ *
115
+ * `valid` is `true` only when the URL is well-formed, its signature matches,
116
+ * its authorized operation equals `expectedOp` (when supplied), and it is used
117
+ * strictly before its expiry relative to `now` (Requirements 8.3, 8.4). When
118
+ * `expectedOp` is omitted the operation check is skipped and only signature and
119
+ * expiry are enforced. `now` defaults to the injected clock so tests can drive
120
+ * expiry deterministically.
121
+ *
122
+ * Failure reasons are reported precisely: `'malformed'` for an unparseable
123
+ * URL, `'signature-mismatch'` for a tampered/incorrectly signed URL,
124
+ * `'operation-mismatch'` when the authorized op differs from `expectedOp`, and
125
+ * `'expired'` when `now` is at or after the expiry.
126
+ */
127
+ verify(url, expectedOp, now) {
128
+ if (this.native !== undefined) {
129
+ return this.native.verify(url, now ?? this.clock());
130
+ }
131
+ const secret = this.signingSecret;
132
+ if (secret === undefined) {
133
+ // Without a secret no simulated URL could have been minted, so any URL is
134
+ // treated as unverifiable rather than silently valid.
135
+ return { valid: false, reason: "malformed" };
136
+ }
137
+ const parsed = parseSignedUrl(url);
138
+ if (parsed === undefined) {
139
+ return { valid: false, reason: "malformed" };
140
+ }
141
+ const { encoded, signature } = parsed;
142
+ const expected = hmacHex(secret, encoded);
143
+ if (!timingSafeEqualHex(signature, expected)) {
144
+ return { valid: false, reason: "signature-mismatch" };
145
+ }
146
+ const payload = decodePayload(encoded);
147
+ if (payload === undefined) {
148
+ return { valid: false, reason: "malformed" };
149
+ }
150
+ if (expectedOp !== undefined && payload.op !== expectedOp) {
151
+ return {
152
+ valid: false,
153
+ reason: "operation-mismatch",
154
+ key: payload.key,
155
+ op: payload.op,
156
+ };
157
+ }
158
+ const at = now ?? this.clock();
159
+ if (at >= payload.expiry) {
160
+ return { valid: false, reason: "expired", key: payload.key, op: payload.op };
161
+ }
162
+ return { valid: true, key: payload.key, op: payload.op };
163
+ }
164
+ /**
165
+ * Resolve the signing secret or throw a descriptive {@link StorageConfigError}.
166
+ * Used only on the simulated minting path, where a secret is mandatory.
167
+ */
168
+ requireSigningSecret() {
169
+ if (this.signingSecret === undefined || this.signingSecret === "") {
170
+ throw new StorageConfigError("Signed URLs require a non-empty \"signingSecret\" in the storage configuration " +
171
+ "when the driver has no native signed-URL capability.");
172
+ }
173
+ return this.signingSecret;
174
+ }
175
+ }
176
+ /** Encode a payload as a compact base64url JSON string. */
177
+ function encodePayload(payload) {
178
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
179
+ }
180
+ /** Decode a base64url JSON payload, returning `undefined` when it is malformed. */
181
+ function decodePayload(encoded) {
182
+ try {
183
+ const json = Buffer.from(encoded, "base64url").toString("utf8");
184
+ const value = JSON.parse(json);
185
+ if (!isSignedPayload(value)) {
186
+ return undefined;
187
+ }
188
+ return value;
189
+ }
190
+ catch {
191
+ return undefined;
192
+ }
193
+ }
194
+ /** Structural guard that a decoded value carries the required payload fields. */
195
+ function isSignedPayload(value) {
196
+ if (typeof value !== "object" || value === null) {
197
+ return false;
198
+ }
199
+ const candidate = value;
200
+ return (candidate.v === 1 &&
201
+ typeof candidate.key === "string" &&
202
+ (candidate.op === "GET" || candidate.op === "PUT" || candidate.op === "DELETE") &&
203
+ typeof candidate.expiry === "number" &&
204
+ Number.isFinite(candidate.expiry));
205
+ }
206
+ /**
207
+ * Parse a simulated signed URL into its encoded payload and signature, returning
208
+ * `undefined` when the scheme is wrong or the separator is missing.
209
+ */
210
+ function parseSignedUrl(url) {
211
+ if (typeof url !== "string" || !url.startsWith(SIGNED_URL_SCHEME)) {
212
+ return undefined;
213
+ }
214
+ const body = url.slice(SIGNED_URL_SCHEME.length);
215
+ const separator = body.lastIndexOf(".");
216
+ if (separator <= 0 || separator >= body.length - 1) {
217
+ return undefined;
218
+ }
219
+ return { encoded: body.slice(0, separator), signature: body.slice(separator + 1) };
220
+ }
221
+ /** Compute the lowercase HMAC-SHA256 hex digest of `data` under `secret`. */
222
+ function hmacHex(secret, data) {
223
+ return createHmac(HMAC_ALGORITHM, secret).update(data).digest("hex");
224
+ }
225
+ /**
226
+ * Constant-time comparison of two hex signature strings. Falls back to `false`
227
+ * for length mismatches (which `timingSafeEqual` would otherwise throw on).
228
+ */
229
+ function timingSafeEqualHex(a, b) {
230
+ const bufferA = Buffer.from(a, "utf8");
231
+ const bufferB = Buffer.from(b, "utf8");
232
+ if (bufferA.length !== bufferB.length) {
233
+ return false;
234
+ }
235
+ return timingSafeEqual(bufferA, bufferB);
236
+ }
237
+ //# sourceMappingURL=signed-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signed-url.js","sourceRoot":"","sources":["../src/signed-url.ts"],"names":[],"mappings":"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"}