@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/README.md ADDED
@@ -0,0 +1,719 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/hassanmubiru/StreetJS/main/docs/assets/images/logo-512.png" alt="StreetJS logo" width="100" height="100">
3
+ </p>
4
+
5
+ # @streetjs/storage
6
+
7
+ StreetJS Core v2 **Pillar 4**: a production-grade, strongly typed, cloud-agnostic
8
+ object storage framework. One consistent API surface across every supported
9
+ provider — switching between Memory, Local, S3, Cloudflare R2, Supabase, Google
10
+ Cloud Storage, Azure Blob, MinIO, and Backblaze B2 is a **configuration change,
11
+ never an application rewrite**.
12
+
13
+ - **Unified facade** — `createStorage(config)` returns a `Storage` whose method
14
+ signatures are identical no matter which driver backs it.
15
+ - **Zero-dependency core** — `streetjs` is the only runtime dependency. The
16
+ `memory` and `local` drivers need nothing else.
17
+ - **Optional provider SDKs** — every cloud provider lives behind its own
18
+ submodule (`@streetjs/storage/s3`, `/r2`, `/supabase`, `/gcs`, `/azure`,
19
+ `/minio`, `/backblaze`) and its SDK is an **optional peer dependency**, pulled
20
+ in only when you actually use that provider.
21
+ - **Full feature set** — object operations, streaming, multipart, resumable
22
+ uploads, signed URLs, validation, typed metadata, versioning, lifecycle rules,
23
+ image processing, a directory API, and search.
24
+ - **Ecosystem bridges** — optional, no-hard-dependency integration with
25
+ `@streetjs/events`, `@streetjs/queue`, and `@streetjs/realtime`, plus reuse of
26
+ the core `MetricsRegistry` / `HealthCheckRegistry`.
27
+ - **In-process test doubles** — `@streetjs/storage/testing`.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ npm install @streetjs/storage
33
+ ```
34
+
35
+ That is all you need for the `memory` and `local` drivers. Cloud providers each
36
+ require their own optional peer SDK — see the per-provider guides below.
37
+
38
+ ## Quick Start
39
+
40
+ ```ts
41
+ import { createStorage } from "@streetjs/storage";
42
+
43
+ // A zero-dependency, in-memory store — great for tests and getting started.
44
+ const storage = createStorage({ provider: "memory" });
45
+
46
+ // Put returns the typed object metadata.
47
+ const meta = await storage.put("avatars/u1.png", pngBytes, {
48
+ contentType: "image/png",
49
+ owner: "user-1",
50
+ });
51
+ console.log(meta.size, meta.etag, meta.checksum, meta.createdAt);
52
+
53
+ // Get returns a discriminated result — no throw on a missing key.
54
+ const result = await storage.get("avatars/u1.png");
55
+ if (result.found) {
56
+ console.log(result.bytes, result.metadata);
57
+ }
58
+
59
+ // The rest of the object surface.
60
+ await storage.exists("avatars/u1.png"); // boolean
61
+ await storage.copy("avatars/u1.png", "avatars/u1-copy.png");
62
+ await storage.move("avatars/u1-copy.png", "trash/u1.png");
63
+ await storage.rename("trash/u1.png", "trash/old.png");
64
+ const items = await storage.list("avatars/"); // [{ key, size, updatedAt }]
65
+ const stat = await storage.stat("avatars/u1.png"); // metadata | null
66
+ await storage.delete("avatars/u1.png");
67
+
68
+ await storage.close();
69
+ ```
70
+
71
+ Use the `local` driver to persist to the filesystem. It requires a `root`:
72
+
73
+ ```ts
74
+ import { createStorage } from "@streetjs/storage";
75
+
76
+ const storage = createStorage({ provider: "local", root: "/var/data/uploads" });
77
+ await storage.put("reports/2024.csv", csvBytes, { contentType: "text/csv" });
78
+ ```
79
+
80
+ ### Configuration
81
+
82
+ `createStorage(config)` accepts:
83
+
84
+ | Field | Type | Purpose |
85
+ |---|---|---|
86
+ | `provider` | `"memory" \| "local" \| string` | Selects a built-in driver, or names a cloud provider when you also pass `driver`. |
87
+ | `driver` | `StorageDriver` | A pre-constructed cloud driver (from a submodule). Takes precedence over `provider`. |
88
+ | `root` | `string` | Filesystem root for the `local` driver. |
89
+ | `clock` | `Clock` | Inject deterministic time (timestamps, signed-URL expiry, lifecycle age). |
90
+ | `validation` | `ValidationConfig` | Upload validation pipeline (see Validation). |
91
+ | `versioning` | `boolean` | Snapshot prior content on overwrite. |
92
+ | `signingSecret` | `string` | HMAC key for simulated signed URLs. |
93
+ | `metrics` | `MetricsRegistry` | Register storage metrics with the core registry. |
94
+ | `health` | `HealthCheckRegistry` | Register storage health checks with the core registry. |
95
+ | `auth` | `AuthLike` | Structural auth bridge for access-control decisions. |
96
+ | `imageCodec` | `ImageCodec` | Structural codec that performs image transforms. |
97
+ | `bridges` | `{ events?, queue?, realtime? }` | Structural ecosystem bridges. |
98
+
99
+ If `provider` is an unknown name and no `driver` is supplied, `createStorage`
100
+ throws a descriptive `StorageConfigError` and returns no instance.
101
+
102
+ ## Providers
103
+
104
+ Every cloud driver exposes **two construction styles**:
105
+
106
+ - A synchronous `create…Driver(client, options?)` that wraps an **injected**
107
+ structural client. No SDK is loaded — ideal for tests and for supplying your
108
+ own client.
109
+ - An asynchronous `connect…Driver(config)` / `create…DriverFromConfig(config)`
110
+ that **builds its own client** by lazily `import()`-ing the provider SDK. If
111
+ the optional peer SDK is not installed, it throws a `StorageConfigError`.
112
+
113
+ For cloud providers you construct the driver, then pass it to `createStorage`
114
+ as a pre-constructed `driver`:
115
+
116
+ ```ts
117
+ const driver = await connectSomeDriver({ /* ... */ });
118
+ const storage = createStorage({ provider: "s3", driver });
119
+ ```
120
+
121
+ ### Memory (`provider: "memory"`)
122
+
123
+ Zero external dependencies, fully in-process. Every feature is simulated over
124
+ in-memory primitives. Best for tests, examples, and ephemeral workloads.
125
+
126
+ ```ts
127
+ import { createStorage } from "@streetjs/storage";
128
+
129
+ const storage = createStorage({ provider: "memory" });
130
+ ```
131
+
132
+ ### Local filesystem (`provider: "local"`)
133
+
134
+ Zero external dependencies. Persists objects under a `root` directory.
135
+
136
+ ```ts
137
+ import { createStorage } from "@streetjs/storage";
138
+
139
+ const storage = createStorage({ provider: "local", root: "/var/data/uploads" });
140
+ ```
141
+
142
+ ### Amazon S3 — `@streetjs/storage/s3`
143
+
144
+ Optional peer dependencies: `@aws-sdk/client-s3` and (for signed URLs)
145
+ `@aws-sdk/s3-request-presigner`.
146
+
147
+ ```ts
148
+ import { createStorage } from "@streetjs/storage";
149
+ import {
150
+ createS3StorageDriver,
151
+ createS3StorageDriverFromConfig,
152
+ } from "@streetjs/storage/s3";
153
+
154
+ // Build a client from config (lazily imports the AWS SDK):
155
+ const driver = await createS3StorageDriverFromConfig({
156
+ bucket: "my-bucket",
157
+ region: "us-east-1",
158
+ credentials: {
159
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
160
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
161
+ },
162
+ });
163
+
164
+ // Or wrap an injected, SDK-free structural client (no AWS SDK needed):
165
+ // const driver = createS3StorageDriver(myS3ClientLike);
166
+
167
+ const storage = createStorage({ provider: "s3", driver });
168
+ ```
169
+
170
+ ### Cloudflare R2 — `@streetjs/storage/r2`
171
+
172
+ R2 speaks the S3 API. Optional peer dependency: `@aws-sdk/client-s3`.
173
+
174
+ ```ts
175
+ import { createStorage } from "@streetjs/storage";
176
+ import {
177
+ connectCloudflareR2Driver,
178
+ createCloudflareR2Driver,
179
+ } from "@streetjs/storage/r2";
180
+
181
+ const driver = await connectCloudflareR2Driver({
182
+ accountId: process.env.R2_ACCOUNT_ID!,
183
+ bucket: "my-bucket",
184
+ accessKeyId: process.env.R2_ACCESS_KEY_ID!,
185
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
186
+ // endpoint defaults to https://<accountId>.r2.cloudflarestorage.com
187
+ });
188
+
189
+ // SDK-free alternative with an injected client:
190
+ // const driver = createCloudflareR2Driver(myS3ClientLike);
191
+
192
+ const storage = createStorage({ provider: "r2", driver });
193
+ ```
194
+
195
+ ### Supabase Storage — `@streetjs/storage/supabase`
196
+
197
+ Optional peer dependency: `@supabase/supabase-js`.
198
+
199
+ ```ts
200
+ import { createStorage } from "@streetjs/storage";
201
+ import {
202
+ connectSupabaseStorageDriver,
203
+ createSupabaseStorageDriver,
204
+ } from "@streetjs/storage/supabase";
205
+
206
+ const driver = await connectSupabaseStorageDriver({
207
+ url: process.env.SUPABASE_URL!,
208
+ key: process.env.SUPABASE_SERVICE_ROLE_KEY!,
209
+ bucket: "uploads",
210
+ });
211
+
212
+ // SDK-free alternative with an injected structural client:
213
+ // const driver = createSupabaseStorageDriver(mySupabaseClientLike);
214
+
215
+ const storage = createStorage({ provider: "supabase", driver });
216
+ ```
217
+
218
+ ### Google Cloud Storage — `@streetjs/storage/gcs`
219
+
220
+ Optional peer dependency: `@google-cloud/storage`.
221
+
222
+ ```ts
223
+ import { createStorage } from "@streetjs/storage";
224
+ import {
225
+ connectGoogleCloudStorageDriver,
226
+ createGoogleCloudStorageDriver,
227
+ } from "@streetjs/storage/gcs";
228
+
229
+ const driver = await connectGoogleCloudStorageDriver({
230
+ bucket: "my-bucket",
231
+ projectId: process.env.GCP_PROJECT_ID,
232
+ keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
233
+ });
234
+
235
+ // SDK-free alternative with an injected structural client:
236
+ // const driver = createGoogleCloudStorageDriver(myGcsClientLike);
237
+
238
+ const storage = createStorage({ provider: "gcs", driver });
239
+ ```
240
+
241
+ ### Azure Blob Storage — `@streetjs/storage/azure`
242
+
243
+ Optional peer dependency: `@azure/storage-blob`.
244
+
245
+ ```ts
246
+ import { createStorage } from "@streetjs/storage";
247
+ import {
248
+ connectAzureBlobDriver,
249
+ createAzureBlobDriver,
250
+ } from "@streetjs/storage/azure";
251
+
252
+ const driver = await connectAzureBlobDriver({
253
+ container: "uploads",
254
+ connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING!,
255
+ });
256
+
257
+ // SDK-free alternative with an injected structural client:
258
+ // const driver = createAzureBlobDriver(myAzureBlobClientLike);
259
+
260
+ const storage = createStorage({ provider: "azure", driver });
261
+ ```
262
+
263
+ ### MinIO — `@streetjs/storage/minio`
264
+
265
+ Self-hosted, S3-compatible. Optional peer dependency: `minio`.
266
+
267
+ ```ts
268
+ import { createStorage } from "@streetjs/storage";
269
+ import { connectMinIODriver, createMinIODriver } from "@streetjs/storage/minio";
270
+
271
+ const driver = await connectMinIODriver({
272
+ bucket: "uploads",
273
+ endPoint: "127.0.0.1",
274
+ port: 9000,
275
+ useSSL: false,
276
+ accessKey: process.env.MINIO_ACCESS_KEY!,
277
+ secretKey: process.env.MINIO_SECRET_KEY!,
278
+ });
279
+
280
+ // SDK-free alternative with an injected S3-compatible client:
281
+ // const driver = createMinIODriver(myS3ClientLike);
282
+
283
+ const storage = createStorage({ provider: "minio", driver });
284
+ ```
285
+
286
+ ### Backblaze B2 — `@streetjs/storage/backblaze`
287
+
288
+ B2 exposes a fully S3-compatible API. Optional peer dependency:
289
+ `@aws-sdk/client-s3`.
290
+
291
+ ```ts
292
+ import { createStorage } from "@streetjs/storage";
293
+ import { createBackblazeB2Driver } from "@streetjs/storage/backblaze";
294
+
295
+ // From connection config (async — lazily imports the S3 SDK):
296
+ const driver = await createBackblazeB2Driver({
297
+ bucket: "my-bucket",
298
+ endpoint: "https://s3.us-west-002.backblazeb2.com",
299
+ region: "us-west-002",
300
+ credentials: {
301
+ accessKeyId: process.env.B2_KEY_ID!, // B2 keyId
302
+ secretAccessKey: process.env.B2_APP_KEY!, // B2 applicationKey
303
+ },
304
+ });
305
+
306
+ // SDK-free alternative with an injected client (sync):
307
+ // const driver = createBackblazeB2Driver(myS3ClientLike);
308
+
309
+ const storage = createStorage({ provider: "backblaze", driver });
310
+ ```
311
+
312
+ ## Streaming
313
+
314
+ Transfer large files without buffering the whole object in memory.
315
+
316
+ ```ts
317
+ import { createReadStream, createWriteStream } from "node:fs";
318
+ import { pipeline } from "node:stream/promises";
319
+
320
+ // Write from a Node Readable.
321
+ await storage.putStream(
322
+ "videos/clip.mp4",
323
+ createReadStream("./clip.mp4"),
324
+ { contentType: "video/mp4" },
325
+ );
326
+
327
+ // Read into a Node Writable.
328
+ const download = await storage.getStream("videos/clip.mp4");
329
+ await pipeline(download, createWriteStream("./out.mp4"));
330
+ ```
331
+
332
+ `getStream` throws when the key does not exist.
333
+
334
+ ## Multipart Uploads
335
+
336
+ Upload very large objects as independent parts, then assemble them.
337
+
338
+ ```ts
339
+ const uploadId = await storage.createMultipartUpload("archives/big.tar", {
340
+ contentType: "application/x-tar",
341
+ });
342
+
343
+ const part1 = await storage.uploadPart(uploadId, 1, chunk1);
344
+ const part2 = await storage.uploadPart(uploadId, 2, chunk2);
345
+
346
+ // Complete assembles the parts in order into a single object.
347
+ const meta = await storage.completeMultipartUpload(uploadId, [part1, part2]);
348
+
349
+ // Or discard everything:
350
+ // await storage.abortMultipartUpload(uploadId);
351
+ ```
352
+
353
+ The concatenation of the ordered parts equals the final object content.
354
+
355
+ ## Resumable Uploads
356
+
357
+ Continue an interrupted upload from the last persisted offset instead of
358
+ restarting.
359
+
360
+ ```ts
361
+ const sessionId = await storage.startUpload("backups/db.sql", {
362
+ contentType: "application/sql",
363
+ });
364
+
365
+ // If a transfer is interrupted, resume with the remaining stream.
366
+ const meta = await storage.resumeUpload(sessionId, remainingStream);
367
+
368
+ // Cancel a session that is not already completing:
369
+ // await storage.cancelUpload(sessionId);
370
+ ```
371
+
372
+ A completed resumable upload yields the same content as an equivalent
373
+ uninterrupted upload.
374
+
375
+ ## Signed URLs
376
+
377
+ Time-limited URLs that authorize exactly one operation (`GET`, `PUT`, or
378
+ `DELETE`) on a key. Configure a `signingSecret` for the simulated signer used by
379
+ the memory/local drivers.
380
+
381
+ ```ts
382
+ const storage = createStorage({ provider: "memory", signingSecret: "s3cr3t" });
383
+
384
+ const getUrl = await storage.signedUrl("reports/2024.pdf", "GET", {
385
+ expiresInMs: 10 * 60 * 1000, // 10 minutes
386
+ });
387
+
388
+ const putUrl = await storage.signedUrl("uploads/incoming.bin", "PUT", {
389
+ expiresInMs: 60_000,
390
+ contentType: "application/octet-stream",
391
+ maxSize: 5 * 1024 * 1024,
392
+ });
393
+ ```
394
+
395
+ Signed-URL options: `expiresInMs`, `headers`, `contentType`, `maxSize`,
396
+ `metadata`. A URL used after its expiration is rejected; used before expiration
397
+ for the authorized operation, it is permitted.
398
+
399
+ ## Validation
400
+
401
+ Run a configurable pipeline before any bytes are persisted. A rejected upload
402
+ aborts with a descriptive `ValidationError` and leaves no partial object.
403
+
404
+ ```ts
405
+ const storage = createStorage({
406
+ provider: "memory",
407
+ validation: {
408
+ allowedMimeTypes: ["image/png", "image/jpeg"],
409
+ allowedExtensions: [".png", ".jpg", ".jpeg"],
410
+ maxSize: 2 * 1024 * 1024,
411
+ filenamePattern: /^[\w./-]+$/,
412
+ requireChecksum: false,
413
+ custom: (input) =>
414
+ input.key.startsWith("public/")
415
+ ? { ok: false, error: "public/ is reserved" }
416
+ : { ok: true },
417
+ },
418
+ });
419
+ ```
420
+
421
+ ## Typed Metadata
422
+
423
+ Every object carries a typed `StorageObjectMetadata`: `key`, `size`,
424
+ `contentType`, `etag`, `checksum`, `owner`, `tenant`, `accessLevel`,
425
+ `createdAt`, `updatedAt`, `custom`, and optional `versionId`. Written metadata
426
+ round-trips through read, and the field set is preserved across providers.
427
+
428
+ ```ts
429
+ await storage.put("docs/spec.md", bytes, {
430
+ contentType: "text/markdown",
431
+ owner: "user-1",
432
+ tenant: "acme",
433
+ accessLevel: "private",
434
+ custom: { project: "atlas" },
435
+ });
436
+
437
+ const meta = await storage.stat("docs/spec.md");
438
+ // meta?.custom.project === "atlas"
439
+ ```
440
+
441
+ ## Access Control
442
+
443
+ Set a per-object `accessLevel` and supply a structural `auth` bridge to make
444
+ authenticated, role-based, and tenant-aware decisions. Access levels: `public`,
445
+ `private`, `signed`, `authenticated`, `role-based`, `tenant-aware`. A denied
446
+ operation throws an `AuthorizationError` before any read or write occurs;
447
+ `public` objects are readable without authentication.
448
+
449
+ ```ts
450
+ const storage = createStorage({
451
+ provider: "memory",
452
+ auth: {
453
+ can: ({ operation, accessLevel, owner }) => {
454
+ if (accessLevel === "public" && operation === "read") return true;
455
+ return owner === currentUserId; // your own logic
456
+ },
457
+ },
458
+ });
459
+ ```
460
+
461
+ ## Lifecycle Rules
462
+
463
+ Apply age/state-based policies. When a rule fires, the events bridge (if wired)
464
+ publishes the corresponding lifecycle event.
465
+
466
+ ```ts
467
+ // Delete objects under "temp/" older than 7 days.
468
+ await storage.applyLifecycle({ type: "delete-after-days", days: 7, prefix: "temp/" });
469
+
470
+ // Archive after N months.
471
+ await storage.applyLifecycle({ type: "archive-after-months", months: 6, prefix: "logs/" });
472
+
473
+ // Expire temporary uploads after a duration.
474
+ await storage.applyLifecycle({ type: "expire-temp-uploads", afterMs: 3_600_000 });
475
+
476
+ // Move to cold storage after N days.
477
+ await storage.applyLifecycle({
478
+ type: "move-to-cold",
479
+ afterDays: 30,
480
+ coldPrefix: "cold/",
481
+ prefix: "media/",
482
+ });
483
+ ```
484
+
485
+ Each returns the `LifecycleOutcome[]` describing which keys were acted on. A
486
+ qualifying object is acted on exactly once.
487
+
488
+ ## Image Processing
489
+
490
+ `storage.images.transform(key, operations)` produces a transformed variant and
491
+ returns its metadata. Supported operations: `resize`, `crop`, `rotate`, `fit`,
492
+ `thumbnail`, `compress`; output formats: `webp`, `avif`, `png`, `jpeg`. Supply a
493
+ structural `imageCodec` in config to perform the pixel work. A non-image source
494
+ yields a descriptive error and never mutates the source.
495
+
496
+ ```ts
497
+ const storage = createStorage({ provider: "memory", imageCodec: myCodec });
498
+
499
+ await storage.put("avatars/u1.png", pngBytes, { contentType: "image/png" });
500
+
501
+ const thumb = await storage.images.transform("avatars/u1.png", {
502
+ resize: { width: 128, height: 128 },
503
+ format: "webp",
504
+ quality: 80,
505
+ });
506
+ ```
507
+
508
+ ## Directory API
509
+
510
+ Prefix-based hierarchy that works even on prefix-only providers.
511
+
512
+ ```ts
513
+ await storage.directory.mkdir("projects/atlas/");
514
+ const children = await storage.directory.listDirectory("projects/"); // immediate children
515
+ const allKeys = await storage.directory.walk("projects/"); // every key beneath
516
+ const { removed } = await storage.directory.removeDirectory("projects/atlas/");
517
+ ```
518
+
519
+ ## Search
520
+
521
+ Filter stored objects by attributes. Only objects matching **every** supplied
522
+ filter are returned; an empty match set returns `[]`.
523
+
524
+ ```ts
525
+ const results = await storage.search({
526
+ prefix: "media/",
527
+ contentType: "image/png",
528
+ owner: "user-1",
529
+ tenant: "acme",
530
+ minSize: 1_024,
531
+ maxSize: 5_000_000,
532
+ updatedAfter: Date.now() - 86_400_000,
533
+ metadata: { project: "atlas" },
534
+ });
535
+ ```
536
+
537
+ ## Queue Integration
538
+
539
+ Hand heavy work to `@streetjs/queue` without a hard dependency. Provide a
540
+ structural `bridges.queue` (`{ dispatch(job, payload) }`). Dispatch is
541
+ fire-and-forget and never breaks a storage operation, so storage keeps working
542
+ whether or not the queue is present.
543
+
544
+ ```ts
545
+ import { bridgeStorageQueue } from "@streetjs/storage";
546
+
547
+ const storage = createStorage({
548
+ provider: "memory",
549
+ bridges: { queue: myQueueLike }, // { dispatch(job, payload) }
550
+ });
551
+
552
+ // Or use the bridge directly to dispatch typed jobs:
553
+ const publisher = bridgeStorageQueue(myQueueLike);
554
+ publisher.thumbnail("avatars/u1.png");
555
+ publisher.virusScan("uploads/incoming.bin");
556
+ ```
557
+
558
+ Job names: `storage.thumbnail`, `storage.virus-scan`, `storage.ocr`,
559
+ `storage.pdf-process`, `storage.transcode`, `storage.image-optimize`,
560
+ `storage.archive`.
561
+
562
+ ## Events Integration
563
+
564
+ Publish typed storage events through `@streetjs/events` (no hard/circular
565
+ dependency). Provide a structural `bridges.events` (`{ publish(event, payload) }`).
566
+
567
+ ```ts
568
+ import { bridgeStorageEvents } from "@streetjs/storage";
569
+
570
+ const storage = createStorage({
571
+ provider: "memory",
572
+ bridges: { events: myEventsLike }, // { publish(event, payload) }
573
+ });
574
+ ```
575
+
576
+ Object mutations publish `storage.uploaded`, `storage.updated`,
577
+ `storage.deleted`, `storage.moved`, `storage.restored`, and `storage.expired`;
578
+ each payload carries the affected `key` and, when available, its metadata.
579
+
580
+ ## Realtime Integration
581
+
582
+ Broadcast upload progress through `@streetjs/realtime` without a hard
583
+ dependency. Provide a structural `bridges.realtime`
584
+ (`{ broadcast(channel, event, payload) }`).
585
+
586
+ ```ts
587
+ import { bridgeStorageRealtime, STORAGE_UPLOAD_CHANNEL } from "@streetjs/storage";
588
+
589
+ const storage = createStorage({
590
+ provider: "memory",
591
+ bridges: { realtime: myRealtimeLike },
592
+ });
593
+ ```
594
+
595
+ Events are broadcast on the `storage.uploads` channel: `upload.started`,
596
+ `upload.progress`, `upload.completed`, and `upload.failed`. A missing realtime
597
+ bridge simply means no broadcasts — uploads proceed unaffected.
598
+
599
+ ## Plugin
600
+
601
+ Register storage in the StreetJS plugin system with `StoragePlugin`. Its options
602
+ extend `StorageConfig`, so provider, bridges, and the `metrics` / `health`
603
+ registries are all supplied through the plugin. The live facade is available via
604
+ `plugin.storage` after load.
605
+
606
+ ```ts
607
+ import { StoragePlugin } from "@streetjs/storage";
608
+
609
+ const plugin = new StoragePlugin({
610
+ provider: "local",
611
+ root: "/var/data/uploads",
612
+ metrics,
613
+ health,
614
+ });
615
+
616
+ await plugin.onLoad(app);
617
+ const storage = plugin.storage!; // the live Storage facade
618
+ ```
619
+
620
+ ## Observability
621
+
622
+ Pass the core `MetricsRegistry` and/or `HealthCheckRegistry` and storage records
623
+ metrics (uploads, downloads, bytes, active/failed uploads, usage, latency,
624
+ multipart, resumable) and registers health checks (connectivity, writability,
625
+ readability, quota). `storage.stats()` returns a live snapshot; `storage.probe()`
626
+ returns a connectivity/quota probe.
627
+
628
+ ```ts
629
+ const storage = createStorage({ provider: "memory", metrics, health });
630
+ const snapshot = storage.stats();
631
+ const probe = await storage.probe();
632
+ ```
633
+
634
+ ## CLI
635
+
636
+ `StorageCommands` provides `make:storage`, `storage:list`, `storage:sync`,
637
+ `storage:clean`, `storage:migrate`, and `storage:verify`. These are **registered
638
+ by your application through the core `CliKernel`** (construct `StorageCommands`
639
+ and register it) — they are not part of the standalone `@streetjs/cli` (`street`)
640
+ built-in command set. Once registered they are invoked as shown below:
641
+
642
+ ```bash
643
+ street make:storage Avatars --dir src/storage --provider local
644
+ street storage:list uploads/ --provider local --root /var/data/uploads
645
+ street storage:clean temp/ --provider local --root /var/data/uploads
646
+ street storage:sync media/ --from-provider local --from-root ./a --to-provider memory
647
+ street storage:migrate media/ --from-provider local --from-root ./a --to-provider local --to-root ./b
648
+ street storage:verify --provider local --root /var/data/uploads
649
+ ```
650
+
651
+ ## Testing
652
+
653
+ `@streetjs/storage/testing` provides in-process, zero-network doubles that
654
+ implement the same contract as production drivers.
655
+
656
+ ```ts
657
+ import {
658
+ MemoryStorage,
659
+ FakeStorage,
660
+ StorageHarness,
661
+ FakeUpload,
662
+ FakeDownload,
663
+ } from "@streetjs/storage/testing";
664
+
665
+ // A real facade over the in-memory driver.
666
+ const storage = MemoryStorage();
667
+
668
+ // A double with an advanceable clock for time-sensitive behavior.
669
+ const fake = new FakeStorage();
670
+ await fake.put("k", "hello");
671
+ fake.advanceTime(60_000); // move time forward (signed-URL expiry, lifecycle age)
672
+
673
+ // A harness bundling a clock, storage, and assertion helpers.
674
+ const harness = new StorageHarness();
675
+ await harness.storage.put("a.txt", "hi");
676
+ await harness.assertExists("a.txt");
677
+ await harness.assertContent("a.txt", "hi");
678
+ await harness.assertKeys("", ["a.txt"]);
679
+
680
+ // Chunked upload / download doubles.
681
+ const upload = new FakeUpload(storage, "big.bin");
682
+ upload.write(chunk1);
683
+ upload.write(chunk2);
684
+ await upload.complete();
685
+
686
+ const download = new FakeDownload(storage, "big.bin");
687
+ const text = await download.text();
688
+ ```
689
+
690
+ `MemoryStorageDriver` is also re-exported for substitution at the driver level.
691
+
692
+ ## Migration
693
+
694
+ Coming from an older, service/provider-based storage layer? The main changes:
695
+
696
+ - Construct with the `createStorage(config)` factory instead of
697
+ instantiating a service class. Pick a driver with `provider` (built-in) or a
698
+ pre-constructed `driver` (cloud).
699
+ - Use the object-operation names `put` / `get` / `delete` (rather than
700
+ `upload` / `download` / `remove`). `get` returns a `{ found, bytes, metadata }`
701
+ result instead of throwing on a missing key.
702
+ - Signed URLs come from `storage.signedUrl(key, op, options)` with
703
+ `op` being `"GET" | "PUT" | "DELETE"` and durations expressed in **milliseconds**
704
+ via `expiresInMs`.
705
+ - Validation, versioning, lifecycle, images, directory, and search are
706
+ first-class methods on the facade rather than separate hooks.
707
+
708
+ To move existing data **between stores**, use the CLI:
709
+
710
+ ```bash
711
+ # Copy (source untouched):
712
+ street storage:sync <prefix> --from-provider <p> --to-provider <p>
713
+ # Move (copy, then remove from source):
714
+ street storage:migrate <prefix> --from-provider <p> --to-provider <p>
715
+ ```
716
+
717
+ ## License
718
+
719
+ MIT