@anvilkit/plugin-asset-manager 0.1.9 → 0.1.10

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 (235) hide show
  1. package/README.md +96 -3
  2. package/dist/adapters/data-url.d.cts +1 -0
  3. package/dist/adapters/data-url.d.cts.map +1 -1
  4. package/dist/adapters/data-url.d.ts +1 -0
  5. package/dist/adapters/data-url.d.ts.map +1 -1
  6. package/dist/adapters/s3-multipart.cjs +425 -0
  7. package/dist/adapters/s3-multipart.d.cts +43 -0
  8. package/dist/adapters/s3-multipart.d.cts.map +1 -0
  9. package/dist/adapters/s3-multipart.d.ts +43 -0
  10. package/dist/adapters/s3-multipart.d.ts.map +1 -0
  11. package/dist/adapters/s3-multipart.js +387 -0
  12. package/dist/adapters/s3-presigned.d.cts +2 -0
  13. package/dist/adapters/s3-presigned.d.cts.map +1 -1
  14. package/dist/adapters/s3-presigned.d.ts +2 -0
  15. package/dist/adapters/s3-presigned.d.ts.map +1 -1
  16. package/dist/i18n/provider.d.cts +1 -0
  17. package/dist/i18n/provider.d.cts.map +1 -1
  18. package/dist/i18n/provider.d.ts +1 -0
  19. package/dist/i18n/provider.d.ts.map +1 -1
  20. package/dist/index.cjs +14 -0
  21. package/dist/index.d.cts +9 -3
  22. package/dist/index.d.cts.map +1 -1
  23. package/dist/index.d.ts +9 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -1
  26. package/dist/plugin.cjs +152 -12
  27. package/dist/plugin.d.cts +49 -1
  28. package/dist/plugin.d.cts.map +1 -1
  29. package/dist/plugin.d.ts +49 -1
  30. package/dist/plugin.d.ts.map +1 -1
  31. package/dist/plugin.js +147 -13
  32. package/dist/sources/composite-source.cjs +3 -0
  33. package/dist/sources/composite-source.d.cts.map +1 -1
  34. package/dist/sources/composite-source.d.ts.map +1 -1
  35. package/dist/sources/composite-source.js +3 -0
  36. package/dist/sources/federated-search.cjs +45 -7
  37. package/dist/sources/federated-search.d.cts.map +1 -1
  38. package/dist/sources/federated-search.d.ts.map +1 -1
  39. package/dist/sources/federated-search.js +45 -7
  40. package/dist/sources/provider.d.cts +5 -0
  41. package/dist/sources/provider.d.cts.map +1 -1
  42. package/dist/sources/provider.d.ts +5 -0
  43. package/dist/sources/provider.d.ts.map +1 -1
  44. package/dist/sources/unsplash/index.d.cts +1 -0
  45. package/dist/sources/unsplash/index.d.cts.map +1 -1
  46. package/dist/sources/unsplash/index.d.ts +1 -0
  47. package/dist/sources/unsplash/index.d.ts.map +1 -1
  48. package/dist/testing/index.d.cts +4 -0
  49. package/dist/testing/index.d.cts.map +1 -1
  50. package/dist/testing/index.d.ts +4 -0
  51. package/dist/testing/index.d.ts.map +1 -1
  52. package/dist/types/categories.d.cts +3 -0
  53. package/dist/types/categories.d.cts.map +1 -1
  54. package/dist/types/categories.d.ts +3 -0
  55. package/dist/types/categories.d.ts.map +1 -1
  56. package/dist/types/data-source.d.cts +9 -0
  57. package/dist/types/data-source.d.cts.map +1 -1
  58. package/dist/types/data-source.d.ts +9 -0
  59. package/dist/types/data-source.d.ts.map +1 -1
  60. package/dist/types/filter.d.cts +11 -0
  61. package/dist/types/filter.d.cts.map +1 -1
  62. package/dist/types/filter.d.ts +11 -0
  63. package/dist/types/filter.d.ts.map +1 -1
  64. package/dist/types/folders.d.cts +2 -0
  65. package/dist/types/folders.d.cts.map +1 -1
  66. package/dist/types/folders.d.ts +2 -0
  67. package/dist/types/folders.d.ts.map +1 -1
  68. package/dist/types/options.d.cts +57 -1
  69. package/dist/types/options.d.cts.map +1 -1
  70. package/dist/types/options.d.ts +57 -1
  71. package/dist/types/options.d.ts.map +1 -1
  72. package/dist/types/resumable.cjs +42 -0
  73. package/dist/types/resumable.d.cts +204 -0
  74. package/dist/types/resumable.d.cts.map +1 -0
  75. package/dist/types/resumable.d.ts +204 -0
  76. package/dist/types/resumable.d.ts.map +1 -0
  77. package/dist/types/resumable.js +4 -0
  78. package/dist/types/transform.cjs +18 -0
  79. package/dist/types/transform.d.cts +45 -0
  80. package/dist/types/transform.d.cts.map +1 -0
  81. package/dist/types/transform.d.ts +45 -0
  82. package/dist/types/transform.d.ts.map +1 -0
  83. package/dist/types/transform.js +1 -0
  84. package/dist/types/types.d.cts +17 -0
  85. package/dist/types/types.d.cts.map +1 -1
  86. package/dist/types/types.d.ts +17 -0
  87. package/dist/types/types.d.ts.map +1 -1
  88. package/dist/types/unsplash.d.cts +2 -0
  89. package/dist/types/unsplash.d.cts.map +1 -1
  90. package/dist/types/unsplash.d.ts +2 -0
  91. package/dist/types/unsplash.d.ts.map +1 -1
  92. package/dist/ui/AssetBrowser.d.cts +3 -1
  93. package/dist/ui/AssetBrowser.d.cts.map +1 -1
  94. package/dist/ui/AssetBrowser.d.ts +3 -1
  95. package/dist/ui/AssetBrowser.d.ts.map +1 -1
  96. package/dist/ui/AssetCommandPalette.d.cts +4 -1
  97. package/dist/ui/AssetCommandPalette.d.cts.map +1 -1
  98. package/dist/ui/AssetCommandPalette.d.ts +4 -1
  99. package/dist/ui/AssetCommandPalette.d.ts.map +1 -1
  100. package/dist/ui/AssetManagerUI.cjs +3 -1
  101. package/dist/ui/AssetManagerUI.d.cts +11 -2
  102. package/dist/ui/AssetManagerUI.d.cts.map +1 -1
  103. package/dist/ui/AssetManagerUI.d.ts +11 -2
  104. package/dist/ui/AssetManagerUI.d.ts.map +1 -1
  105. package/dist/ui/AssetManagerUI.js +3 -1
  106. package/dist/ui/DeleteAssetDialog.d.cts +4 -1
  107. package/dist/ui/DeleteAssetDialog.d.cts.map +1 -1
  108. package/dist/ui/DeleteAssetDialog.d.ts +4 -1
  109. package/dist/ui/DeleteAssetDialog.d.ts.map +1 -1
  110. package/dist/ui/DeleteFolderDialog.d.cts +4 -1
  111. package/dist/ui/DeleteFolderDialog.d.cts.map +1 -1
  112. package/dist/ui/DeleteFolderDialog.d.ts +4 -1
  113. package/dist/ui/DeleteFolderDialog.d.ts.map +1 -1
  114. package/dist/ui/EmptyFolderState.d.cts +4 -1
  115. package/dist/ui/EmptyFolderState.d.cts.map +1 -1
  116. package/dist/ui/EmptyFolderState.d.ts +4 -1
  117. package/dist/ui/EmptyFolderState.d.ts.map +1 -1
  118. package/dist/ui/FolderBreadcrumb.d.cts +4 -1
  119. package/dist/ui/FolderBreadcrumb.d.cts.map +1 -1
  120. package/dist/ui/FolderBreadcrumb.d.ts +4 -1
  121. package/dist/ui/FolderBreadcrumb.d.ts.map +1 -1
  122. package/dist/ui/FolderNameDialog.d.cts +3 -1
  123. package/dist/ui/FolderNameDialog.d.cts.map +1 -1
  124. package/dist/ui/FolderNameDialog.d.ts +3 -1
  125. package/dist/ui/FolderNameDialog.d.ts.map +1 -1
  126. package/dist/ui/FolderTree.d.cts +4 -1
  127. package/dist/ui/FolderTree.d.cts.map +1 -1
  128. package/dist/ui/FolderTree.d.ts +4 -1
  129. package/dist/ui/FolderTree.d.ts.map +1 -1
  130. package/dist/ui/MetadataPanel.d.cts +4 -1
  131. package/dist/ui/MetadataPanel.d.cts.map +1 -1
  132. package/dist/ui/MetadataPanel.d.ts +4 -1
  133. package/dist/ui/MetadataPanel.d.ts.map +1 -1
  134. package/dist/ui/MoveTargetPicker.d.cts +3 -1
  135. package/dist/ui/MoveTargetPicker.d.cts.map +1 -1
  136. package/dist/ui/MoveTargetPicker.d.ts +3 -1
  137. package/dist/ui/MoveTargetPicker.d.ts.map +1 -1
  138. package/dist/ui/ReplaceAssetDialog.cjs +7 -2
  139. package/dist/ui/ReplaceAssetDialog.d.cts +5 -2
  140. package/dist/ui/ReplaceAssetDialog.d.cts.map +1 -1
  141. package/dist/ui/ReplaceAssetDialog.d.ts +5 -2
  142. package/dist/ui/ReplaceAssetDialog.d.ts.map +1 -1
  143. package/dist/ui/ReplaceAssetDialog.js +7 -2
  144. package/dist/ui/UnsplashPanel.d.cts +4 -1
  145. package/dist/ui/UnsplashPanel.d.cts.map +1 -1
  146. package/dist/ui/UnsplashPanel.d.ts +4 -1
  147. package/dist/ui/UnsplashPanel.d.ts.map +1 -1
  148. package/dist/ui/UploadButton.cjs +7 -2
  149. package/dist/ui/UploadButton.d.cts +6 -2
  150. package/dist/ui/UploadButton.d.cts.map +1 -1
  151. package/dist/ui/UploadButton.d.ts +6 -2
  152. package/dist/ui/UploadButton.d.ts.map +1 -1
  153. package/dist/ui/UploadButton.js +7 -2
  154. package/dist/utils/asset-reference.cjs +87 -4
  155. package/dist/utils/asset-reference.d.cts +30 -6
  156. package/dist/utils/asset-reference.d.cts.map +1 -1
  157. package/dist/utils/asset-reference.d.ts +30 -6
  158. package/dist/utils/asset-reference.d.ts.map +1 -1
  159. package/dist/utils/asset-reference.js +83 -3
  160. package/dist/utils/csp.cjs +16 -0
  161. package/dist/utils/csp.d.cts +23 -0
  162. package/dist/utils/csp.d.cts.map +1 -1
  163. package/dist/utils/csp.d.ts +23 -0
  164. package/dist/utils/csp.d.ts.map +1 -1
  165. package/dist/utils/csp.js +16 -0
  166. package/dist/utils/data-source.cjs +19 -5
  167. package/dist/utils/data-source.d.cts +5 -1
  168. package/dist/utils/data-source.d.cts.map +1 -1
  169. package/dist/utils/data-source.d.ts +5 -1
  170. package/dist/utils/data-source.d.ts.map +1 -1
  171. package/dist/utils/data-source.js +19 -5
  172. package/dist/utils/errors.d.cts +5 -0
  173. package/dist/utils/errors.d.cts.map +1 -1
  174. package/dist/utils/errors.d.ts +5 -0
  175. package/dist/utils/errors.d.ts.map +1 -1
  176. package/dist/utils/query-param-transform.cjs +101 -0
  177. package/dist/utils/query-param-transform.d.cts +46 -0
  178. package/dist/utils/query-param-transform.d.cts.map +1 -0
  179. package/dist/utils/query-param-transform.d.ts +46 -0
  180. package/dist/utils/query-param-transform.d.ts.map +1 -0
  181. package/dist/utils/query-param-transform.js +60 -0
  182. package/dist/utils/registry.cjs +3 -0
  183. package/dist/utils/registry.d.cts +8 -0
  184. package/dist/utils/registry.d.cts.map +1 -1
  185. package/dist/utils/registry.d.ts +8 -0
  186. package/dist/utils/registry.d.ts.map +1 -1
  187. package/dist/utils/registry.js +3 -0
  188. package/dist/utils/resolver.cjs +19 -11
  189. package/dist/utils/resolver.d.cts +24 -0
  190. package/dist/utils/resolver.d.cts.map +1 -1
  191. package/dist/utils/resolver.d.ts +24 -0
  192. package/dist/utils/resolver.d.ts.map +1 -1
  193. package/dist/utils/resolver.js +19 -11
  194. package/dist/utils/retry.d.cts +2 -0
  195. package/dist/utils/retry.d.cts.map +1 -1
  196. package/dist/utils/retry.d.ts +2 -0
  197. package/dist/utils/retry.d.ts.map +1 -1
  198. package/dist/utils/run-resumable-upload.cjs +160 -0
  199. package/dist/utils/run-resumable-upload.d.cts +56 -0
  200. package/dist/utils/run-resumable-upload.d.cts.map +1 -0
  201. package/dist/utils/run-resumable-upload.d.ts +56 -0
  202. package/dist/utils/run-resumable-upload.d.ts.map +1 -0
  203. package/dist/utils/run-resumable-upload.js +122 -0
  204. package/dist/utils/sniff-file-type.cjs +209 -0
  205. package/dist/utils/sniff-file-type.d.cts +25 -0
  206. package/dist/utils/sniff-file-type.d.cts.map +1 -0
  207. package/dist/utils/sniff-file-type.d.ts +25 -0
  208. package/dist/utils/sniff-file-type.d.ts.map +1 -0
  209. package/dist/utils/sniff-file-type.js +164 -0
  210. package/dist/utils/studio-asset-source.cjs +11 -6
  211. package/dist/utils/studio-asset-source.d.cts +5 -1
  212. package/dist/utils/studio-asset-source.d.cts.map +1 -1
  213. package/dist/utils/studio-asset-source.d.ts +5 -1
  214. package/dist/utils/studio-asset-source.d.ts.map +1 -1
  215. package/dist/utils/studio-asset-source.js +11 -6
  216. package/dist/utils/upload-session-store.cjs +125 -0
  217. package/dist/utils/upload-session-store.d.cts +55 -0
  218. package/dist/utils/upload-session-store.d.cts.map +1 -0
  219. package/dist/utils/upload-session-store.d.ts +55 -0
  220. package/dist/utils/upload-session-store.d.ts.map +1 -0
  221. package/dist/utils/upload-session-store.js +84 -0
  222. package/dist/utils/validate-upload-result.cjs +9 -1
  223. package/dist/utils/validate-upload-result.d.cts +1 -0
  224. package/dist/utils/validate-upload-result.d.cts.map +1 -1
  225. package/dist/utils/validate-upload-result.d.ts +1 -0
  226. package/dist/utils/validate-upload-result.d.ts.map +1 -1
  227. package/dist/utils/validate-upload-result.js +9 -1
  228. package/dist/version.cjs +1 -1
  229. package/dist/version.d.cts +1 -1
  230. package/dist/version.d.cts.map +1 -1
  231. package/dist/version.d.ts +1 -1
  232. package/dist/version.d.ts.map +1 -1
  233. package/dist/version.js +1 -1
  234. package/meta/config.json +1 -1
  235. package/package.json +40 -10
@@ -0,0 +1,164 @@
1
+ const ascii = (text)=>Array.from(text, (ch)=>ch.charCodeAt(0));
2
+ const SIGNATURES = [
3
+ {
4
+ mime: "image/png",
5
+ parts: [
6
+ {
7
+ offset: 0,
8
+ bytes: [
9
+ 0x89,
10
+ 0x50,
11
+ 0x4e,
12
+ 0x47,
13
+ 0x0d,
14
+ 0x0a,
15
+ 0x1a,
16
+ 0x0a
17
+ ]
18
+ }
19
+ ]
20
+ },
21
+ {
22
+ mime: "image/jpeg",
23
+ parts: [
24
+ {
25
+ offset: 0,
26
+ bytes: [
27
+ 0xff,
28
+ 0xd8,
29
+ 0xff
30
+ ]
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ mime: "image/gif",
36
+ parts: [
37
+ {
38
+ offset: 0,
39
+ bytes: ascii("GIF8")
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ mime: "image/bmp",
45
+ parts: [
46
+ {
47
+ offset: 0,
48
+ bytes: ascii("BM")
49
+ }
50
+ ]
51
+ },
52
+ {
53
+ mime: "image/webp",
54
+ parts: [
55
+ {
56
+ offset: 0,
57
+ bytes: ascii("RIFF")
58
+ },
59
+ {
60
+ offset: 8,
61
+ bytes: ascii("WEBP")
62
+ }
63
+ ]
64
+ },
65
+ {
66
+ mime: "audio/wav",
67
+ parts: [
68
+ {
69
+ offset: 0,
70
+ bytes: ascii("RIFF")
71
+ },
72
+ {
73
+ offset: 8,
74
+ bytes: ascii("WAVE")
75
+ }
76
+ ]
77
+ },
78
+ {
79
+ mime: "video/webm",
80
+ parts: [
81
+ {
82
+ offset: 0,
83
+ bytes: [
84
+ 0x1a,
85
+ 0x45,
86
+ 0xdf,
87
+ 0xa3
88
+ ]
89
+ }
90
+ ]
91
+ },
92
+ {
93
+ mime: "audio/ogg",
94
+ parts: [
95
+ {
96
+ offset: 0,
97
+ bytes: ascii("OggS")
98
+ }
99
+ ]
100
+ },
101
+ {
102
+ mime: "audio/mpeg",
103
+ parts: [
104
+ {
105
+ offset: 0,
106
+ bytes: ascii("ID3")
107
+ }
108
+ ]
109
+ },
110
+ {
111
+ mime: "application/pdf",
112
+ parts: [
113
+ {
114
+ offset: 0,
115
+ bytes: ascii("%PDF")
116
+ }
117
+ ]
118
+ }
119
+ ];
120
+ const FTYP_BRAND_MIME = {
121
+ avif: "image/avif",
122
+ avis: "image/avif",
123
+ heic: "image/heic",
124
+ heix: "image/heic",
125
+ heim: "image/heic",
126
+ heis: "image/heic",
127
+ mif1: "image/heic",
128
+ isom: "video/mp4",
129
+ iso2: "video/mp4",
130
+ mp41: "video/mp4",
131
+ mp42: "video/mp4",
132
+ avc1: "video/mp4",
133
+ dash: "video/mp4",
134
+ "qt ": "video/quicktime"
135
+ };
136
+ const FTYP = ascii("ftyp");
137
+ const SNIFF_BYTE_COUNT = 16;
138
+ function detectMimeFromBytes(bytes) {
139
+ if (matchesAt(bytes, {
140
+ offset: 4,
141
+ bytes: FTYP
142
+ })) {
143
+ const brand = readAscii(bytes, 8, 4);
144
+ return void 0 !== brand ? FTYP_BRAND_MIME[brand] : void 0;
145
+ }
146
+ for (const signature of SIGNATURES)if (signature.parts.every((part)=>matchesAt(bytes, part))) return signature.mime;
147
+ }
148
+ function readAscii(bytes, offset, length) {
149
+ if (offset + length > bytes.length) return;
150
+ let out = "";
151
+ for(let i = 0; i < length; i += 1)out += String.fromCharCode(bytes[offset + i]);
152
+ return out;
153
+ }
154
+ function matchesAt(bytes, part) {
155
+ if (part.offset + part.bytes.length > bytes.length) return false;
156
+ for(let i = 0; i < part.bytes.length; i += 1)if (bytes[part.offset + i] !== part.bytes[i]) return false;
157
+ return true;
158
+ }
159
+ async function sniffFileMime(file) {
160
+ if ("function" != typeof file.slice) return;
161
+ const buffer = await file.slice(0, SNIFF_BYTE_COUNT).arrayBuffer();
162
+ return detectMimeFromBytes(new Uint8Array(buffer));
163
+ }
164
+ export { SNIFF_BYTE_COUNT, detectMimeFromBytes, sniffFileMime };
@@ -34,10 +34,11 @@ __webpack_require__.d(__webpack_exports__, {
34
34
  toStudioAsset: ()=>toStudioAsset
35
35
  });
36
36
  const external_asset_reference_cjs_namespaceObject = require("./asset-reference.cjs");
37
+ const external_errors_cjs_namespaceObject = require("./errors.cjs");
37
38
  const external_infer_kind_cjs_namespaceObject = require("./infer-kind.cjs");
38
39
  const MAX_CONCURRENT_UPLOADS = 3;
39
40
  function createStudioAssetSource(options) {
40
- const { registry, upload, getThumbnail } = options;
41
+ const { registry, upload, getThumbnail, onDelete } = options;
41
42
  const maxConcurrent = Math.max(1, options.maxConcurrentUploads ?? MAX_CONCURRENT_UPLOADS);
42
43
  const project = (entry)=>toStudioAsset(entry, getThumbnail);
43
44
  const uploadListeners = new Set();
@@ -141,16 +142,17 @@ function createStudioAssetSource(options) {
141
142
  signal?.removeEventListener("abort", abortFromSignal);
142
143
  }
143
144
  },
144
- delete (assetId) {
145
- registry.delete(assetId);
146
- return Promise.resolve();
145
+ async delete (assetId) {
146
+ const removed = registry.get(assetId);
147
+ if (!registry.delete(assetId)) throw makeUnknownAssetMutationError("delete", assetId);
148
+ if (void 0 !== removed) await onDelete?.(removed);
147
149
  },
148
150
  rename (assetId, nextName) {
149
- registry.rename(assetId, nextName);
151
+ if (void 0 === registry.rename(assetId, nextName)) return Promise.reject(makeUnknownAssetMutationError("rename", assetId));
150
152
  return Promise.resolve();
151
153
  },
152
154
  setTags (assetId, tags) {
153
- registry.setTags(assetId, tags);
155
+ if (void 0 === registry.setTags(assetId, tags)) return Promise.reject(makeUnknownAssetMutationError("set tags on", assetId));
154
156
  return Promise.resolve();
155
157
  },
156
158
  async replace (assetId, file) {
@@ -207,6 +209,9 @@ function deriveThumbnailUrl(entry, kind, getThumbnail) {
207
209
  if (void 0 !== getThumbnail) return getThumbnail(entry);
208
210
  return "image" === kind ? entry.url : void 0;
209
211
  }
212
+ function makeUnknownAssetMutationError(operation, assetId) {
213
+ return new external_errors_cjs_namespaceObject.AssetSourceError("ASSET_MUTATION_REJECTED", `Cannot ${operation} unknown asset "${assetId}".`);
214
+ }
210
215
  function deriveFallbackName(entry) {
211
216
  const tail = entry.url.split(/[/?#]/).filter(Boolean).pop();
212
217
  if (void 0 !== tail && "" !== tail && !tail.startsWith("data:")) return tail;
@@ -12,13 +12,14 @@
12
12
  * registry so the IR resolver and the sidebar see the same data.
13
13
  */
14
14
  import type { StudioAsset, StudioAssetKind, StudioAssetSource } from "@anvilkit/core/types";
15
- import type { AssetRegistry, UploadAdapterOptions, UploadResult } from "../types/types.js";
15
+ import type { AssetDeletedHook, AssetRegistry, UploadAdapterOptions, UploadResult } from "../types/types.js";
16
16
  /**
17
17
  * Default concurrency cap for batched uploads. Editors typically drag
18
18
  * 1–10 files at a time; three concurrent uploads strikes a balance
19
19
  * between throughput and politeness to host endpoints.
20
20
  */
21
21
  export declare const MAX_CONCURRENT_UPLOADS = 3;
22
+ /** Inputs needed to bridge the registry into core's studio asset source API. */
22
23
  export interface CreateStudioAssetSourceOptions {
23
24
  readonly registry: AssetRegistry;
24
25
  /**
@@ -41,7 +42,10 @@ export interface CreateStudioAssetSourceOptions {
41
42
  * sequential behavior.
42
43
  */
43
44
  readonly maxConcurrentUploads?: number;
45
+ /** Fired with the removed record after a successful delete. */
46
+ readonly onDelete?: AssetDeletedHook;
44
47
  }
48
+ /** Adapt the upload registry into core's `StudioAssetSource` contract. */
45
49
  export declare function createStudioAssetSource(options: CreateStudioAssetSourceOptions): StudioAssetSource;
46
50
  /**
47
51
  * Project a registry `UploadResult` into the sidebar's `StudioAsset` shape.
@@ -1 +1 @@
1
- {"version":3,"file":"studio-asset-source.d.cts","sourceRoot":"","sources":["../../src/utils/studio-asset-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,eAAe,EAGf,iBAAiB,EAGjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACX,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,MAAM,mBAAmB,CAAC;AAI3B;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,CAChB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,oBAAoB,KAC1B,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,8BAA8B,GACrC,iBAAiB,CAqLnB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC5B,KAAK,EAAE,YAAY,EACnB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,GACxD,WAAW,CAqBb;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAEzE"}
1
+ {"version":3,"file":"studio-asset-source.d.cts","sourceRoot":"","sources":["../../src/utils/studio-asset-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,eAAe,EAGf,iBAAiB,EAGjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACX,gBAAgB,EAChB,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,MAAM,mBAAmB,CAAC;AAK3B;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,gFAAgF;AAChF,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,CAChB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,oBAAoB,KAC1B,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,8BAA8B,GACrC,iBAAiB,CA8LnB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC5B,KAAK,EAAE,YAAY,EACnB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,GACxD,WAAW,CAqBb;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAEzE"}
@@ -12,13 +12,14 @@
12
12
  * registry so the IR resolver and the sidebar see the same data.
13
13
  */
14
14
  import type { StudioAsset, StudioAssetKind, StudioAssetSource } from "@anvilkit/core/types";
15
- import type { AssetRegistry, UploadAdapterOptions, UploadResult } from "../types/types.js";
15
+ import type { AssetDeletedHook, AssetRegistry, UploadAdapterOptions, UploadResult } from "../types/types.js";
16
16
  /**
17
17
  * Default concurrency cap for batched uploads. Editors typically drag
18
18
  * 1–10 files at a time; three concurrent uploads strikes a balance
19
19
  * between throughput and politeness to host endpoints.
20
20
  */
21
21
  export declare const MAX_CONCURRENT_UPLOADS = 3;
22
+ /** Inputs needed to bridge the registry into core's studio asset source API. */
22
23
  export interface CreateStudioAssetSourceOptions {
23
24
  readonly registry: AssetRegistry;
24
25
  /**
@@ -41,7 +42,10 @@ export interface CreateStudioAssetSourceOptions {
41
42
  * sequential behavior.
42
43
  */
43
44
  readonly maxConcurrentUploads?: number;
45
+ /** Fired with the removed record after a successful delete. */
46
+ readonly onDelete?: AssetDeletedHook;
44
47
  }
48
+ /** Adapt the upload registry into core's `StudioAssetSource` contract. */
45
49
  export declare function createStudioAssetSource(options: CreateStudioAssetSourceOptions): StudioAssetSource;
46
50
  /**
47
51
  * Project a registry `UploadResult` into the sidebar's `StudioAsset` shape.
@@ -1 +1 @@
1
- {"version":3,"file":"studio-asset-source.d.ts","sourceRoot":"","sources":["../../src/utils/studio-asset-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,eAAe,EAGf,iBAAiB,EAGjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACX,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,MAAM,mBAAmB,CAAC;AAI3B;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,CAChB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,oBAAoB,KAC1B,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,8BAA8B,GACrC,iBAAiB,CAqLnB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC5B,KAAK,EAAE,YAAY,EACnB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,GACxD,WAAW,CAqBb;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAEzE"}
1
+ {"version":3,"file":"studio-asset-source.d.ts","sourceRoot":"","sources":["../../src/utils/studio-asset-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,eAAe,EAGf,iBAAiB,EAGjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACX,gBAAgB,EAChB,aAAa,EACb,oBAAoB,EACpB,YAAY,EACZ,MAAM,mBAAmB,CAAC;AAK3B;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,gFAAgF;AAChF,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,CAChB,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,oBAAoB,KAC1B,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,8BAA8B,GACrC,iBAAiB,CA8LnB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC5B,KAAK,EAAE,YAAY,EACnB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,GACxD,WAAW,CAqBb;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAEzE"}
@@ -1,8 +1,9 @@
1
1
  import { createAssetReference } from "./asset-reference.js";
2
+ import { AssetSourceError } from "./errors.js";
2
3
  import { inferAssetKind } from "./infer-kind.js";
3
4
  const MAX_CONCURRENT_UPLOADS = 3;
4
5
  function createStudioAssetSource(options) {
5
- const { registry, upload, getThumbnail } = options;
6
+ const { registry, upload, getThumbnail, onDelete } = options;
6
7
  const maxConcurrent = Math.max(1, options.maxConcurrentUploads ?? MAX_CONCURRENT_UPLOADS);
7
8
  const project = (entry)=>toStudioAsset(entry, getThumbnail);
8
9
  const uploadListeners = new Set();
@@ -106,16 +107,17 @@ function createStudioAssetSource(options) {
106
107
  signal?.removeEventListener("abort", abortFromSignal);
107
108
  }
108
109
  },
109
- delete (assetId) {
110
- registry.delete(assetId);
111
- return Promise.resolve();
110
+ async delete (assetId) {
111
+ const removed = registry.get(assetId);
112
+ if (!registry.delete(assetId)) throw makeUnknownAssetMutationError("delete", assetId);
113
+ if (void 0 !== removed) await onDelete?.(removed);
112
114
  },
113
115
  rename (assetId, nextName) {
114
- registry.rename(assetId, nextName);
116
+ if (void 0 === registry.rename(assetId, nextName)) return Promise.reject(makeUnknownAssetMutationError("rename", assetId));
115
117
  return Promise.resolve();
116
118
  },
117
119
  setTags (assetId, tags) {
118
- registry.setTags(assetId, tags);
120
+ if (void 0 === registry.setTags(assetId, tags)) return Promise.reject(makeUnknownAssetMutationError("set tags on", assetId));
119
121
  return Promise.resolve();
120
122
  },
121
123
  async replace (assetId, file) {
@@ -172,6 +174,9 @@ function deriveThumbnailUrl(entry, kind, getThumbnail) {
172
174
  if (void 0 !== getThumbnail) return getThumbnail(entry);
173
175
  return "image" === kind ? entry.url : void 0;
174
176
  }
177
+ function makeUnknownAssetMutationError(operation, assetId) {
178
+ return new AssetSourceError("ASSET_MUTATION_REJECTED", `Cannot ${operation} unknown asset "${assetId}".`);
179
+ }
175
180
  function deriveFallbackName(entry) {
176
181
  const tail = entry.url.split(/[/?#]/).filter(Boolean).pop();
177
182
  if (void 0 !== tail && "" !== tail && !tail.startsWith("data:")) return tail;
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ const DEFAULT_KEY_PREFIX = "anvilkit:asset-upload";
31
+ function fingerprintFile(file) {
32
+ return `${encodeURIComponent(file.name)}:${file.size}:${file.lastModified}`;
33
+ }
34
+ function createUploadSessionStore(options = {}) {
35
+ const storage = options.storage ?? resolveDefaultStorage();
36
+ const prefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
37
+ const keyOf = (file)=>`${prefix}:${fingerprintFile(file)}`;
38
+ return {
39
+ load (file) {
40
+ const key = keyOf(file);
41
+ let raw;
42
+ try {
43
+ raw = storage.getItem(key);
44
+ } catch {
45
+ return;
46
+ }
47
+ if (null === raw) return;
48
+ let parsed;
49
+ try {
50
+ parsed = JSON.parse(raw);
51
+ } catch {
52
+ dropQuietly(storage, key);
53
+ return;
54
+ }
55
+ if (!isPersistedUploadSession(parsed)) return void dropQuietly(storage, key);
56
+ return parsed;
57
+ },
58
+ save (file, session) {
59
+ const serialized = JSON.stringify(session);
60
+ try {
61
+ storage.setItem(keyOf(file), serialized);
62
+ } catch {}
63
+ },
64
+ clear (file) {
65
+ dropQuietly(storage, keyOf(file));
66
+ }
67
+ };
68
+ }
69
+ function dropQuietly(storage, key) {
70
+ try {
71
+ storage.removeItem(key);
72
+ } catch {}
73
+ }
74
+ function resolveDefaultStorage() {
75
+ try {
76
+ const ls = globalThis.localStorage;
77
+ if (ls) {
78
+ const probe = `${DEFAULT_KEY_PREFIX}:__probe__`;
79
+ ls.setItem(probe, "1");
80
+ ls.removeItem(probe);
81
+ return ls;
82
+ }
83
+ } catch {}
84
+ return createMemoryStorage();
85
+ }
86
+ function createMemoryStorage() {
87
+ const map = new Map();
88
+ return {
89
+ getItem: (key)=>map.get(key) ?? null,
90
+ setItem: (key, value)=>{
91
+ map.set(key, value);
92
+ },
93
+ removeItem: (key)=>{
94
+ map.delete(key);
95
+ }
96
+ };
97
+ }
98
+ function isPersistedUploadSession(value) {
99
+ if (!isJsonObject(value)) return false;
100
+ const v = value;
101
+ if ("string" != typeof v.uploadId || "" === v.uploadId) return false;
102
+ if (!isPositiveSafeInteger(v.partSize)) return false;
103
+ if (void 0 !== v.meta && !isJsonObject(v.meta)) return false;
104
+ if (!Array.isArray(v.parts)) return false;
105
+ return v.parts.every((part)=>isJsonObject(part) && isPositiveSafeInteger(part.partNumber) && "string" == typeof part.etag);
106
+ }
107
+ function isJsonObject(value) {
108
+ return null !== value && "object" == typeof value && !Array.isArray(value);
109
+ }
110
+ function isPositiveSafeInteger(value) {
111
+ return "number" == typeof value && Number.isSafeInteger(value) && value > 0;
112
+ }
113
+ __webpack_require__.d(__webpack_exports__, {
114
+ createUploadSessionStore: ()=>createUploadSessionStore,
115
+ fingerprintFile: ()=>fingerprintFile
116
+ });
117
+ exports.createUploadSessionStore = __webpack_exports__.createUploadSessionStore;
118
+ exports.fingerprintFile = __webpack_exports__.fingerprintFile;
119
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
120
+ "createUploadSessionStore",
121
+ "fingerprintFile"
122
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
123
+ Object.defineProperty(exports, '__esModule', {
124
+ value: true
125
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @file Built-in {@link UploadSessionStore} implementation (PRD 0004 §5.1 — M2).
3
+ *
4
+ * Persists in-progress resumable-upload sessions so an interrupted upload can
5
+ * resume after a reload. Keyed by a stable file fingerprint (name + size +
6
+ * lastModified): the same picked file resumes, a different file does not.
7
+ *
8
+ * Backed by `localStorage` by default with a silent in-memory fallback when the
9
+ * Web Storage API is unavailable or throws (SSR, Node tests, privacy mode).
10
+ * Every storage call is wrapped — persistence is an *optimization*, never a
11
+ * correctness dependency, so quota/availability errors degrade to "no resume"
12
+ * rather than failing the upload.
13
+ *
14
+ * The interface ({@link UploadSessionStore}) lives in `types/resumable.ts`
15
+ * alongside the rest of the contract; this module is the implementation only,
16
+ * so `types → utils` never forms an import cycle.
17
+ *
18
+ * @experimental Public surface may change before v1.0.
19
+ */
20
+ import type { UploadSessionStore } from "../types/resumable.js";
21
+ /**
22
+ * Minimal subset of the Web Storage API the session store relies on. Declared
23
+ * locally (rather than referencing the DOM `Storage` lib type) so a host can
24
+ * supply `sessionStorage`, a namespaced wrapper, or a test double without
25
+ * pulling in DOM typings.
26
+ */
27
+ export interface UploadSessionStorage {
28
+ getItem(key: string): string | null;
29
+ setItem(key: string, value: string): void;
30
+ removeItem(key: string): void;
31
+ }
32
+ /** Options for {@link createUploadSessionStore}. */
33
+ export interface CreateUploadSessionStoreOptions {
34
+ /**
35
+ * Backing storage. Defaults to `globalThis.localStorage` when usable,
36
+ * otherwise a process-lifetime in-memory map.
37
+ */
38
+ readonly storage?: UploadSessionStorage;
39
+ /** Key namespace. Defaults to `"anvilkit:asset-upload"`. */
40
+ readonly keyPrefix?: string;
41
+ }
42
+ /**
43
+ * Stable identity for an upload across reloads: the same file (by name, byte
44
+ * length, and last-modified time) maps to the same key, while any edit changes
45
+ * it. `name` is percent-encoded so the `:` delimiter can't be spoofed by a
46
+ * filename.
47
+ */
48
+ export declare function fingerprintFile(file: File): string;
49
+ /**
50
+ * Create the built-in {@link UploadSessionStore}. Synchronous (localStorage is
51
+ * synchronous); the contract's optionally-async return types let custom hosts
52
+ * back it with IndexedDB or a remote service.
53
+ */
54
+ export declare function createUploadSessionStore(options?: CreateUploadSessionStoreOptions): UploadSessionStore;
55
+ //# sourceMappingURL=upload-session-store.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-session-store.d.cts","sourceRoot":"","sources":["../../src/utils/upload-session-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAEX,kBAAkB,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,oDAAoD;AACpD,MAAM,WAAW,+BAA+B;IAC/C;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,oBAAoB,CAAC;IACxC,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAElD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,GAAE,+BAAoC,GAC3C,kBAAkB,CA+CpB"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @file Built-in {@link UploadSessionStore} implementation (PRD 0004 §5.1 — M2).
3
+ *
4
+ * Persists in-progress resumable-upload sessions so an interrupted upload can
5
+ * resume after a reload. Keyed by a stable file fingerprint (name + size +
6
+ * lastModified): the same picked file resumes, a different file does not.
7
+ *
8
+ * Backed by `localStorage` by default with a silent in-memory fallback when the
9
+ * Web Storage API is unavailable or throws (SSR, Node tests, privacy mode).
10
+ * Every storage call is wrapped — persistence is an *optimization*, never a
11
+ * correctness dependency, so quota/availability errors degrade to "no resume"
12
+ * rather than failing the upload.
13
+ *
14
+ * The interface ({@link UploadSessionStore}) lives in `types/resumable.ts`
15
+ * alongside the rest of the contract; this module is the implementation only,
16
+ * so `types → utils` never forms an import cycle.
17
+ *
18
+ * @experimental Public surface may change before v1.0.
19
+ */
20
+ import type { UploadSessionStore } from "../types/resumable.js";
21
+ /**
22
+ * Minimal subset of the Web Storage API the session store relies on. Declared
23
+ * locally (rather than referencing the DOM `Storage` lib type) so a host can
24
+ * supply `sessionStorage`, a namespaced wrapper, or a test double without
25
+ * pulling in DOM typings.
26
+ */
27
+ export interface UploadSessionStorage {
28
+ getItem(key: string): string | null;
29
+ setItem(key: string, value: string): void;
30
+ removeItem(key: string): void;
31
+ }
32
+ /** Options for {@link createUploadSessionStore}. */
33
+ export interface CreateUploadSessionStoreOptions {
34
+ /**
35
+ * Backing storage. Defaults to `globalThis.localStorage` when usable,
36
+ * otherwise a process-lifetime in-memory map.
37
+ */
38
+ readonly storage?: UploadSessionStorage;
39
+ /** Key namespace. Defaults to `"anvilkit:asset-upload"`. */
40
+ readonly keyPrefix?: string;
41
+ }
42
+ /**
43
+ * Stable identity for an upload across reloads: the same file (by name, byte
44
+ * length, and last-modified time) maps to the same key, while any edit changes
45
+ * it. `name` is percent-encoded so the `:` delimiter can't be spoofed by a
46
+ * filename.
47
+ */
48
+ export declare function fingerprintFile(file: File): string;
49
+ /**
50
+ * Create the built-in {@link UploadSessionStore}. Synchronous (localStorage is
51
+ * synchronous); the contract's optionally-async return types let custom hosts
52
+ * back it with IndexedDB or a remote service.
53
+ */
54
+ export declare function createUploadSessionStore(options?: CreateUploadSessionStoreOptions): UploadSessionStore;
55
+ //# sourceMappingURL=upload-session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-session-store.d.ts","sourceRoot":"","sources":["../../src/utils/upload-session-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAEX,kBAAkB,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,oDAAoD;AACpD,MAAM,WAAW,+BAA+B;IAC/C;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,oBAAoB,CAAC;IACxC,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAElD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,GAAE,+BAAoC,GAC3C,kBAAkB,CA+CpB"}
@@ -0,0 +1,84 @@
1
+ const DEFAULT_KEY_PREFIX = "anvilkit:asset-upload";
2
+ function fingerprintFile(file) {
3
+ return `${encodeURIComponent(file.name)}:${file.size}:${file.lastModified}`;
4
+ }
5
+ function createUploadSessionStore(options = {}) {
6
+ const storage = options.storage ?? resolveDefaultStorage();
7
+ const prefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
8
+ const keyOf = (file)=>`${prefix}:${fingerprintFile(file)}`;
9
+ return {
10
+ load (file) {
11
+ const key = keyOf(file);
12
+ let raw;
13
+ try {
14
+ raw = storage.getItem(key);
15
+ } catch {
16
+ return;
17
+ }
18
+ if (null === raw) return;
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(raw);
22
+ } catch {
23
+ dropQuietly(storage, key);
24
+ return;
25
+ }
26
+ if (!isPersistedUploadSession(parsed)) return void dropQuietly(storage, key);
27
+ return parsed;
28
+ },
29
+ save (file, session) {
30
+ const serialized = JSON.stringify(session);
31
+ try {
32
+ storage.setItem(keyOf(file), serialized);
33
+ } catch {}
34
+ },
35
+ clear (file) {
36
+ dropQuietly(storage, keyOf(file));
37
+ }
38
+ };
39
+ }
40
+ function dropQuietly(storage, key) {
41
+ try {
42
+ storage.removeItem(key);
43
+ } catch {}
44
+ }
45
+ function resolveDefaultStorage() {
46
+ try {
47
+ const ls = globalThis.localStorage;
48
+ if (ls) {
49
+ const probe = `${DEFAULT_KEY_PREFIX}:__probe__`;
50
+ ls.setItem(probe, "1");
51
+ ls.removeItem(probe);
52
+ return ls;
53
+ }
54
+ } catch {}
55
+ return createMemoryStorage();
56
+ }
57
+ function createMemoryStorage() {
58
+ const map = new Map();
59
+ return {
60
+ getItem: (key)=>map.get(key) ?? null,
61
+ setItem: (key, value)=>{
62
+ map.set(key, value);
63
+ },
64
+ removeItem: (key)=>{
65
+ map.delete(key);
66
+ }
67
+ };
68
+ }
69
+ function isPersistedUploadSession(value) {
70
+ if (!isJsonObject(value)) return false;
71
+ const v = value;
72
+ if ("string" != typeof v.uploadId || "" === v.uploadId) return false;
73
+ if (!isPositiveSafeInteger(v.partSize)) return false;
74
+ if (void 0 !== v.meta && !isJsonObject(v.meta)) return false;
75
+ if (!Array.isArray(v.parts)) return false;
76
+ return v.parts.every((part)=>isJsonObject(part) && isPositiveSafeInteger(part.partNumber) && "string" == typeof part.etag);
77
+ }
78
+ function isJsonObject(value) {
79
+ return null !== value && "object" == typeof value && !Array.isArray(value);
80
+ }
81
+ function isPositiveSafeInteger(value) {
82
+ return "number" == typeof value && Number.isSafeInteger(value) && value > 0;
83
+ }
84
+ export { createUploadSessionStore, fingerprintFile };
@@ -74,7 +74,7 @@ function normalizeAllowedUrl(input, options) {
74
74
  if (!isSchemeAllowed(scheme, options)) throw new external_errors_cjs_namespaceObject.AssetValidationError("DISALLOWED_UPLOAD_URL_SCHEME", `Upload adapter returned URL scheme "${scheme}" which is not in the allowlist.`);
75
75
  if ("http" === scheme || "https" === scheme || "blob" === scheme) assertNoPathTraversal(sanitized);
76
76
  if ("http" === scheme || "https" === scheme) assertNoMixedScriptHostname(sanitized, options);
77
- return candidate;
77
+ return sanitized;
78
78
  }
79
79
  function isSchemeAllowed(scheme, options) {
80
80
  if (ALWAYS_ALLOWED_SCHEMES.has(scheme)) return true;
@@ -179,6 +179,14 @@ function stripUndefinedMeta(meta) {
179
179
  } : {},
180
180
  ...void 0 !== meta.height ? {
181
181
  height: meta.height
182
+ } : {},
183
+ ...void 0 !== meta.hash ? {
184
+ hash: meta.hash
185
+ } : {},
186
+ ...void 0 !== meta.attribution ? {
187
+ attribution: Object.freeze({
188
+ ...meta.attribution
189
+ })
182
190
  } : {}
183
191
  };
184
192
  return Object.freeze(nextMeta);