@anvilkit/plugin-asset-manager 0.1.8 → 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.
- package/README.md +96 -3
- package/dist/adapters/data-url.d.cts +1 -0
- package/dist/adapters/data-url.d.cts.map +1 -1
- package/dist/adapters/data-url.d.ts +1 -0
- package/dist/adapters/data-url.d.ts.map +1 -1
- package/dist/adapters/s3-multipart.cjs +425 -0
- package/dist/adapters/s3-multipart.d.cts +43 -0
- package/dist/adapters/s3-multipart.d.cts.map +1 -0
- package/dist/adapters/s3-multipart.d.ts +43 -0
- package/dist/adapters/s3-multipart.d.ts.map +1 -0
- package/dist/adapters/s3-multipart.js +387 -0
- package/dist/adapters/s3-presigned.d.cts +2 -0
- package/dist/adapters/s3-presigned.d.cts.map +1 -1
- package/dist/adapters/s3-presigned.d.ts +2 -0
- package/dist/adapters/s3-presigned.d.ts.map +1 -1
- package/dist/i18n/provider.d.cts +1 -0
- package/dist/i18n/provider.d.cts.map +1 -1
- package/dist/i18n/provider.d.ts +1 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/index.cjs +14 -0
- package/dist/index.d.cts +9 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/plugin.cjs +152 -12
- package/dist/plugin.d.cts +49 -1
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.ts +49 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +147 -13
- package/dist/sources/composite-source.cjs +3 -0
- package/dist/sources/composite-source.d.cts.map +1 -1
- package/dist/sources/composite-source.d.ts.map +1 -1
- package/dist/sources/composite-source.js +3 -0
- package/dist/sources/federated-search.cjs +45 -7
- package/dist/sources/federated-search.d.cts.map +1 -1
- package/dist/sources/federated-search.d.ts.map +1 -1
- package/dist/sources/federated-search.js +45 -7
- package/dist/sources/provider.d.cts +5 -0
- package/dist/sources/provider.d.cts.map +1 -1
- package/dist/sources/provider.d.ts +5 -0
- package/dist/sources/provider.d.ts.map +1 -1
- package/dist/sources/unsplash/index.d.cts +1 -0
- package/dist/sources/unsplash/index.d.cts.map +1 -1
- package/dist/sources/unsplash/index.d.ts +1 -0
- package/dist/sources/unsplash/index.d.ts.map +1 -1
- package/dist/testing/index.d.cts +4 -0
- package/dist/testing/index.d.cts.map +1 -1
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/types/categories.d.cts +3 -0
- package/dist/types/categories.d.cts.map +1 -1
- package/dist/types/categories.d.ts +3 -0
- package/dist/types/categories.d.ts.map +1 -1
- package/dist/types/data-source.d.cts +9 -0
- package/dist/types/data-source.d.cts.map +1 -1
- package/dist/types/data-source.d.ts +9 -0
- package/dist/types/data-source.d.ts.map +1 -1
- package/dist/types/filter.d.cts +11 -0
- package/dist/types/filter.d.cts.map +1 -1
- package/dist/types/filter.d.ts +11 -0
- package/dist/types/filter.d.ts.map +1 -1
- package/dist/types/folders.d.cts +2 -0
- package/dist/types/folders.d.cts.map +1 -1
- package/dist/types/folders.d.ts +2 -0
- package/dist/types/folders.d.ts.map +1 -1
- package/dist/types/options.d.cts +57 -1
- package/dist/types/options.d.cts.map +1 -1
- package/dist/types/options.d.ts +57 -1
- package/dist/types/options.d.ts.map +1 -1
- package/dist/types/resumable.cjs +42 -0
- package/dist/types/resumable.d.cts +204 -0
- package/dist/types/resumable.d.cts.map +1 -0
- package/dist/types/resumable.d.ts +204 -0
- package/dist/types/resumable.d.ts.map +1 -0
- package/dist/types/resumable.js +4 -0
- package/dist/types/transform.cjs +18 -0
- package/dist/types/transform.d.cts +45 -0
- package/dist/types/transform.d.cts.map +1 -0
- package/dist/types/transform.d.ts +45 -0
- package/dist/types/transform.d.ts.map +1 -0
- package/dist/types/transform.js +1 -0
- package/dist/types/types.d.cts +17 -0
- package/dist/types/types.d.cts.map +1 -1
- package/dist/types/types.d.ts +17 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/unsplash.d.cts +2 -0
- package/dist/types/unsplash.d.cts.map +1 -1
- package/dist/types/unsplash.d.ts +2 -0
- package/dist/types/unsplash.d.ts.map +1 -1
- package/dist/ui/AssetBrowser.d.cts +3 -1
- package/dist/ui/AssetBrowser.d.cts.map +1 -1
- package/dist/ui/AssetBrowser.d.ts +3 -1
- package/dist/ui/AssetBrowser.d.ts.map +1 -1
- package/dist/ui/AssetCommandPalette.d.cts +4 -1
- package/dist/ui/AssetCommandPalette.d.cts.map +1 -1
- package/dist/ui/AssetCommandPalette.d.ts +4 -1
- package/dist/ui/AssetCommandPalette.d.ts.map +1 -1
- package/dist/ui/AssetManagerUI.cjs +3 -1
- package/dist/ui/AssetManagerUI.d.cts +11 -2
- package/dist/ui/AssetManagerUI.d.cts.map +1 -1
- package/dist/ui/AssetManagerUI.d.ts +11 -2
- package/dist/ui/AssetManagerUI.d.ts.map +1 -1
- package/dist/ui/AssetManagerUI.js +3 -1
- package/dist/ui/DeleteAssetDialog.d.cts +4 -1
- package/dist/ui/DeleteAssetDialog.d.cts.map +1 -1
- package/dist/ui/DeleteAssetDialog.d.ts +4 -1
- package/dist/ui/DeleteAssetDialog.d.ts.map +1 -1
- package/dist/ui/DeleteFolderDialog.d.cts +4 -1
- package/dist/ui/DeleteFolderDialog.d.cts.map +1 -1
- package/dist/ui/DeleteFolderDialog.d.ts +4 -1
- package/dist/ui/DeleteFolderDialog.d.ts.map +1 -1
- package/dist/ui/EmptyFolderState.d.cts +4 -1
- package/dist/ui/EmptyFolderState.d.cts.map +1 -1
- package/dist/ui/EmptyFolderState.d.ts +4 -1
- package/dist/ui/EmptyFolderState.d.ts.map +1 -1
- package/dist/ui/FolderBreadcrumb.d.cts +4 -1
- package/dist/ui/FolderBreadcrumb.d.cts.map +1 -1
- package/dist/ui/FolderBreadcrumb.d.ts +4 -1
- package/dist/ui/FolderBreadcrumb.d.ts.map +1 -1
- package/dist/ui/FolderNameDialog.d.cts +3 -1
- package/dist/ui/FolderNameDialog.d.cts.map +1 -1
- package/dist/ui/FolderNameDialog.d.ts +3 -1
- package/dist/ui/FolderNameDialog.d.ts.map +1 -1
- package/dist/ui/FolderTree.d.cts +4 -1
- package/dist/ui/FolderTree.d.cts.map +1 -1
- package/dist/ui/FolderTree.d.ts +4 -1
- package/dist/ui/FolderTree.d.ts.map +1 -1
- package/dist/ui/MetadataPanel.d.cts +4 -1
- package/dist/ui/MetadataPanel.d.cts.map +1 -1
- package/dist/ui/MetadataPanel.d.ts +4 -1
- package/dist/ui/MetadataPanel.d.ts.map +1 -1
- package/dist/ui/MoveTargetPicker.d.cts +3 -1
- package/dist/ui/MoveTargetPicker.d.cts.map +1 -1
- package/dist/ui/MoveTargetPicker.d.ts +3 -1
- package/dist/ui/MoveTargetPicker.d.ts.map +1 -1
- package/dist/ui/ReplaceAssetDialog.cjs +7 -2
- package/dist/ui/ReplaceAssetDialog.d.cts +5 -2
- package/dist/ui/ReplaceAssetDialog.d.cts.map +1 -1
- package/dist/ui/ReplaceAssetDialog.d.ts +5 -2
- package/dist/ui/ReplaceAssetDialog.d.ts.map +1 -1
- package/dist/ui/ReplaceAssetDialog.js +7 -2
- package/dist/ui/UnsplashPanel.d.cts +4 -1
- package/dist/ui/UnsplashPanel.d.cts.map +1 -1
- package/dist/ui/UnsplashPanel.d.ts +4 -1
- package/dist/ui/UnsplashPanel.d.ts.map +1 -1
- package/dist/ui/UploadButton.cjs +7 -2
- package/dist/ui/UploadButton.d.cts +6 -2
- package/dist/ui/UploadButton.d.cts.map +1 -1
- package/dist/ui/UploadButton.d.ts +6 -2
- package/dist/ui/UploadButton.d.ts.map +1 -1
- package/dist/ui/UploadButton.js +7 -2
- package/dist/utils/asset-reference.cjs +87 -4
- package/dist/utils/asset-reference.d.cts +30 -6
- package/dist/utils/asset-reference.d.cts.map +1 -1
- package/dist/utils/asset-reference.d.ts +30 -6
- package/dist/utils/asset-reference.d.ts.map +1 -1
- package/dist/utils/asset-reference.js +83 -3
- package/dist/utils/csp.cjs +16 -0
- package/dist/utils/csp.d.cts +23 -0
- package/dist/utils/csp.d.cts.map +1 -1
- package/dist/utils/csp.d.ts +23 -0
- package/dist/utils/csp.d.ts.map +1 -1
- package/dist/utils/csp.js +16 -0
- package/dist/utils/data-source.cjs +19 -5
- package/dist/utils/data-source.d.cts +5 -1
- package/dist/utils/data-source.d.cts.map +1 -1
- package/dist/utils/data-source.d.ts +5 -1
- package/dist/utils/data-source.d.ts.map +1 -1
- package/dist/utils/data-source.js +19 -5
- package/dist/utils/errors.d.cts +5 -0
- package/dist/utils/errors.d.cts.map +1 -1
- package/dist/utils/errors.d.ts +5 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/query-param-transform.cjs +101 -0
- package/dist/utils/query-param-transform.d.cts +46 -0
- package/dist/utils/query-param-transform.d.cts.map +1 -0
- package/dist/utils/query-param-transform.d.ts +46 -0
- package/dist/utils/query-param-transform.d.ts.map +1 -0
- package/dist/utils/query-param-transform.js +60 -0
- package/dist/utils/registry.cjs +3 -0
- package/dist/utils/registry.d.cts +8 -0
- package/dist/utils/registry.d.cts.map +1 -1
- package/dist/utils/registry.d.ts +8 -0
- package/dist/utils/registry.d.ts.map +1 -1
- package/dist/utils/registry.js +3 -0
- package/dist/utils/resolver.cjs +19 -11
- package/dist/utils/resolver.d.cts +24 -0
- package/dist/utils/resolver.d.cts.map +1 -1
- package/dist/utils/resolver.d.ts +24 -0
- package/dist/utils/resolver.d.ts.map +1 -1
- package/dist/utils/resolver.js +19 -11
- package/dist/utils/retry.d.cts +2 -0
- package/dist/utils/retry.d.cts.map +1 -1
- package/dist/utils/retry.d.ts +2 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/run-resumable-upload.cjs +160 -0
- package/dist/utils/run-resumable-upload.d.cts +56 -0
- package/dist/utils/run-resumable-upload.d.cts.map +1 -0
- package/dist/utils/run-resumable-upload.d.ts +56 -0
- package/dist/utils/run-resumable-upload.d.ts.map +1 -0
- package/dist/utils/run-resumable-upload.js +122 -0
- package/dist/utils/sniff-file-type.cjs +209 -0
- package/dist/utils/sniff-file-type.d.cts +25 -0
- package/dist/utils/sniff-file-type.d.cts.map +1 -0
- package/dist/utils/sniff-file-type.d.ts +25 -0
- package/dist/utils/sniff-file-type.d.ts.map +1 -0
- package/dist/utils/sniff-file-type.js +164 -0
- package/dist/utils/studio-asset-source.cjs +11 -6
- package/dist/utils/studio-asset-source.d.cts +5 -1
- package/dist/utils/studio-asset-source.d.cts.map +1 -1
- package/dist/utils/studio-asset-source.d.ts +5 -1
- package/dist/utils/studio-asset-source.d.ts.map +1 -1
- package/dist/utils/studio-asset-source.js +11 -6
- package/dist/utils/upload-session-store.cjs +125 -0
- package/dist/utils/upload-session-store.d.cts +55 -0
- package/dist/utils/upload-session-store.d.cts.map +1 -0
- package/dist/utils/upload-session-store.d.ts +55 -0
- package/dist/utils/upload-session-store.d.ts.map +1 -0
- package/dist/utils/upload-session-store.js +84 -0
- package/dist/utils/validate-upload-result.cjs +9 -1
- package/dist/utils/validate-upload-result.d.cts +1 -0
- package/dist/utils/validate-upload-result.d.cts.map +1 -1
- package/dist/utils/validate-upload-result.d.ts +1 -0
- package/dist/utils/validate-upload-result.d.ts.map +1 -1
- package/dist/utils/validate-upload-result.js +9 -1
- package/dist/version.cjs +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.cts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/meta/config.json +1 -1
- package/package.json +42 -12
|
@@ -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.
|
|
146
|
-
|
|
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;
|
|
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;
|
|
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.
|
|
111
|
-
|
|
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
|
|
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);
|