@stapel/cdn-react 0.1.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.
- package/CHANGELOG.md +1 -0
- package/MODULE.md +124 -0
- package/README.md +103 -0
- package/dist/api/cdnApi.d.ts +122 -0
- package/dist/api/cdnApi.d.ts.map +1 -0
- package/dist/api/cdnApi.js +27 -0
- package/dist/api/cdnApi.js.map +1 -0
- package/dist/api/generated/schema.d.ts +1050 -0
- package/dist/api/generated/schema.d.ts.map +1 -0
- package/dist/api/generated/schema.js +2 -0
- package/dist/api/generated/schema.js.map +1 -0
- package/dist/api/types.d.ts +76 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/default/ErrorAlert.d.ts +8 -0
- package/dist/default/ErrorAlert.d.ts.map +1 -0
- package/dist/default/ErrorAlert.js +26 -0
- package/dist/default/ErrorAlert.js.map +1 -0
- package/dist/default/ImageUploadField.d.ts +12 -0
- package/dist/default/ImageUploadField.d.ts.map +1 -0
- package/dist/default/ImageUploadField.js +48 -0
- package/dist/default/ImageUploadField.js.map +1 -0
- package/dist/default/MediaGalleryField.d.ts +14 -0
- package/dist/default/MediaGalleryField.d.ts.map +1 -0
- package/dist/default/MediaGalleryField.js +65 -0
- package/dist/default/MediaGalleryField.js.map +1 -0
- package/dist/default/index.d.ts +15 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +13 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/phase.d.ts +17 -0
- package/dist/default/phase.d.ts.map +1 -0
- package/dist/default/phase.js +25 -0
- package/dist/default/phase.js.map +1 -0
- package/dist/flows/registry.d.ts +28 -0
- package/dist/flows/registry.d.ts.map +1 -0
- package/dist/flows/registry.js +26 -0
- package/dist/flows/registry.js.map +1 -0
- package/dist/headless/CdnProvider.d.ts +18 -0
- package/dist/headless/CdnProvider.d.ts.map +1 -0
- package/dist/headless/CdnProvider.js +14 -0
- package/dist/headless/CdnProvider.js.map +1 -0
- package/dist/headless/ImageUpload.d.ts +18 -0
- package/dist/headless/ImageUpload.d.ts.map +1 -0
- package/dist/headless/ImageUpload.js +16 -0
- package/dist/headless/ImageUpload.js.map +1 -0
- package/dist/headless/MediaUploader.d.ts +31 -0
- package/dist/headless/MediaUploader.d.ts.map +1 -0
- package/dist/headless/MediaUploader.js +30 -0
- package/dist/headless/MediaUploader.js.map +1 -0
- package/dist/headless/useUploadImage.d.ts +33 -0
- package/dist/headless/useUploadImage.d.ts.map +1 -0
- package/dist/headless/useUploadImage.js +118 -0
- package/dist/headless/useUploadImage.js.map +1 -0
- package/dist/headless/useUploadPreview.d.ts +22 -0
- package/dist/headless/useUploadPreview.d.ts.map +1 -0
- package/dist/headless/useUploadPreview.js +37 -0
- package/dist/headless/useUploadPreview.js.map +1 -0
- package/dist/headless/useUploadQueue.d.ts +83 -0
- package/dist/headless/useUploadQueue.d.ts.map +1 -0
- package/dist/headless/useUploadQueue.js +0 -0
- package/dist/headless/useUploadQueue.js.map +1 -0
- package/dist/i18n/errorsMap.d.ts +12 -0
- package/dist/i18n/errorsMap.d.ts.map +1 -0
- package/dist/i18n/errorsMap.js +22 -0
- package/dist/i18n/errorsMap.js.map +1 -0
- package/dist/i18n/es.d.ts +17 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +65 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.es.gen.js +58 -0
- package/dist/i18n/generated/errors.es.gen.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +353 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.gen.js +180 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.js +58 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
- package/dist/i18n/keys.d.ts +61 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +110 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +21 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +70 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/dist/model/context.d.ts +11 -0
- package/dist/model/context.d.ts.map +1 -0
- package/dist/model/context.js +15 -0
- package/dist/model/context.js.map +1 -0
- package/dist/model/hash.d.ts +36 -0
- package/dist/model/hash.d.ts.map +1 -0
- package/dist/model/hash.js +59 -0
- package/dist/model/hash.js.map +1 -0
- package/dist/model/limits.d.ts +86 -0
- package/dist/model/limits.d.ts.map +1 -0
- package/dist/model/limits.js +163 -0
- package/dist/model/limits.js.map +1 -0
- package/dist/model/queries.d.ts +29 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +32 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +18 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +18 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/refs.d.ts +54 -0
- package/dist/model/refs.d.ts.map +1 -0
- package/dist/model/refs.js +82 -0
- package/dist/model/refs.js.map +1 -0
- package/dist/model/runtime.d.ts +40 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +23 -0
- package/dist/model/runtime.js.map +1 -0
- package/dist/model/upload.d.ts +88 -0
- package/dist/model/upload.d.ts.map +1 -0
- package/dist/model/upload.js +254 -0
- package/dist/model/upload.js.map +1 -0
- package/llms.txt +83 -0
- package/manifest.json +601 -0
- package/package.json +120 -0
- package/src/analytics/generated/events.json +7 -0
- package/src/api/cdnApi.ts +187 -0
- package/src/api/generated/schema.ts +1052 -0
- package/src/api/types.ts +86 -0
- package/src/default/ErrorAlert.tsx +43 -0
- package/src/default/ImageUploadField.tsx +131 -0
- package/src/default/MediaGalleryField.tsx +233 -0
- package/src/default/index.ts +14 -0
- package/src/default/phase.ts +34 -0
- package/src/flows/registry.ts +38 -0
- package/src/headless/CdnProvider.tsx +19 -0
- package/src/headless/ImageUpload.tsx +24 -0
- package/src/headless/MediaUploader.tsx +43 -0
- package/src/headless/useUploadImage.ts +155 -0
- package/src/headless/useUploadPreview.ts +48 -0
- package/src/headless/useUploadQueue.ts +411 -0
- package/src/i18n/errorsMap.ts +33 -0
- package/src/i18n/es.ts +81 -0
- package/src/i18n/generated/errors.es.gen.ts +64 -0
- package/src/i18n/generated/errors.gen.ts +211 -0
- package/src/i18n/generated/errors.json +403 -0
- package/src/i18n/generated/errors.ru.gen.ts +64 -0
- package/src/i18n/keys.ts +131 -0
- package/src/i18n/ru.ts +85 -0
- package/src/index.ts +154 -0
- package/src/model/context.tsx +25 -0
- package/src/model/hash.ts +67 -0
- package/src/model/limits.ts +207 -0
- package/src/model/queries.ts +64 -0
- package/src/model/queryKeys.ts +23 -0
- package/src/model/refs.ts +95 -0
- package/src/model/runtime.ts +52 -0
- package/src/model/upload.ts +365 -0
- package/tsconfig.json +26 -0
package/package.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stapel/cdn-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless React pair for stapel-cdn: a typed media-upload client whose flow is dedup-first — SHA-256 the bytes, ask file/exists/, and skip the POST entirely when the CDN already holds them. A phase-shaped upload bag (validate → hash → check → upload → variants), an ordered queue with per-item cancel/retry/remove and a max, object-URL previews with the revoke built in (@stapel/core's useObjectUrlPreview), and the opaque `<type>/<hash>` reference every consuming module actually stores. Client-side limits mirror the backend's own declared ceilings and never refuse what the server would accept. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/usestapel/stapel-react.git",
|
|
9
|
+
"directory": "packages/cdn-react"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./default": {
|
|
21
|
+
"types": "./dist/default/index.d.ts",
|
|
22
|
+
"default": "./dist/default/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./i18n/ru": {
|
|
25
|
+
"types": "./dist/i18n/ru.d.ts",
|
|
26
|
+
"default": "./dist/i18n/ru.js"
|
|
27
|
+
},
|
|
28
|
+
"./i18n/es": {
|
|
29
|
+
"types": "./dist/i18n/es.d.ts",
|
|
30
|
+
"default": "./dist/i18n/es.js"
|
|
31
|
+
},
|
|
32
|
+
"./manifest": "./manifest.json",
|
|
33
|
+
"./manifest.json": "./manifest.json",
|
|
34
|
+
"./llms.txt": "./llms.txt",
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"src",
|
|
40
|
+
"tsconfig.json",
|
|
41
|
+
"README.md",
|
|
42
|
+
"MODULE.md",
|
|
43
|
+
"CHANGELOG.md",
|
|
44
|
+
"manifest.json",
|
|
45
|
+
"llms.txt"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"test": "tsc -p tsconfig.demo.json && vitest run --exclude test/prodBundlePurity.test.ts",
|
|
50
|
+
"test:pack": "vitest run test/prodBundlePurity.test.ts",
|
|
51
|
+
"lint": "eslint .",
|
|
52
|
+
"size": "size-limit"
|
|
53
|
+
},
|
|
54
|
+
"size-limit": [
|
|
55
|
+
{
|
|
56
|
+
"name": "index — headless pair (client + dedup-first upload flow + queue), no antd",
|
|
57
|
+
"path": "dist/index.js",
|
|
58
|
+
"limit": "8 KB"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "default — the antd upload skin must stay out of the main bundle",
|
|
62
|
+
"path": "dist/default/index.js",
|
|
63
|
+
"limit": "8 KB"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "i18n/ru — the Russian bundle is opt-in, not carried by the main entry",
|
|
67
|
+
"path": "dist/i18n/ru.js",
|
|
68
|
+
"limit": "3 KB"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "i18n/es — the Spanish bundle is opt-in, not carried by the main entry",
|
|
72
|
+
"path": "dist/i18n/es.js",
|
|
73
|
+
"limit": "2.5 KB"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"@stapel/core": ">=0.15.0 <1.0.0",
|
|
78
|
+
"@stapel/image": ">=0.2.0",
|
|
79
|
+
"@stapel/tokens-antd": ">=0.5.0",
|
|
80
|
+
"@tanstack/react-query": "^5.0.0",
|
|
81
|
+
"antd": ">=5.20.0 <7",
|
|
82
|
+
"react": ">=19"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"@stapel/image": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"@stapel/tokens-antd": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"antd": {
|
|
92
|
+
"optional": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"devDependencies": {
|
|
96
|
+
"@size-limit/preset-small-lib": "^11.2.0",
|
|
97
|
+
"@stapel/core": "workspace:^",
|
|
98
|
+
"@stapel/image": "workspace:^",
|
|
99
|
+
"@stapel/showcase": "workspace:^",
|
|
100
|
+
"@stapel/tokens": "workspace:^",
|
|
101
|
+
"@stapel/tokens-antd": "workspace:^",
|
|
102
|
+
"@tanstack/react-query": "^5.81.0",
|
|
103
|
+
"@testing-library/react": "^16.3.0",
|
|
104
|
+
"@types/react": "^19.1.0",
|
|
105
|
+
"@types/react-dom": "^19.1.0",
|
|
106
|
+
"antd": "^6.5.0",
|
|
107
|
+
"jsdom": "^26.1.0",
|
|
108
|
+
"react": "^19.1.0",
|
|
109
|
+
"react-dom": "^19.1.0",
|
|
110
|
+
"size-limit": "^11.2.0",
|
|
111
|
+
"typescript": "^5.8.3",
|
|
112
|
+
"vitest": "^3.2.4"
|
|
113
|
+
},
|
|
114
|
+
"engines": {
|
|
115
|
+
"node": ">=22"
|
|
116
|
+
},
|
|
117
|
+
"publishConfig": {
|
|
118
|
+
"access": "public"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { StapelClient } from "@stapel/core";
|
|
2
|
+
import type {
|
|
3
|
+
CdnFileExistsResponse,
|
|
4
|
+
CdnFileUploadResponse,
|
|
5
|
+
CdnImageUploadResponse,
|
|
6
|
+
CdnVideoUploadResponse,
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The pair's typed operation surface — one method per stapel-cdn endpoint a
|
|
11
|
+
* browser may call, bound to the injected {@link StapelClient} (the per-module
|
|
12
|
+
* override seam of frontend-standard §7.2). Paths are relative to the
|
|
13
|
+
* runtime's `baseUrl` (`/cdn/api/v1/`).
|
|
14
|
+
*
|
|
15
|
+
* ── The two endpoints that are NOT here, and why ───────────────────────────
|
|
16
|
+
*
|
|
17
|
+
* `POST /refs/sync/` is `IsServiceRequest`: it is how one BACKEND tells the
|
|
18
|
+
* CDN that an entity now references a set of media, and a browser cannot
|
|
19
|
+
* authenticate as a service. A pair that exposed it would invite a screen to
|
|
20
|
+
* call something that can only answer 403 (`stapel_cdn/views.py`, RefSyncView).
|
|
21
|
+
* Reference bookkeeping is the consuming module's server-side job — the
|
|
22
|
+
* storefront's part is to hand `images_draft` to stapel-listings and let it
|
|
23
|
+
* sync.
|
|
24
|
+
*
|
|
25
|
+
* `GET /images/{image_type}/random/` is `IsStaffUser` and exists to let an
|
|
26
|
+
* admin UI grab a test image. Same argument.
|
|
27
|
+
*
|
|
28
|
+
* Both stay in the generated schema (and therefore in `manifest.json`, which
|
|
29
|
+
* lists the whole contract), so nothing is hidden; they are simply not this
|
|
30
|
+
* pair's surface.
|
|
31
|
+
*
|
|
32
|
+
* ── Upload is a POST of `multipart/form-data`, not a presigned PUT ─────────
|
|
33
|
+
*
|
|
34
|
+
* This is the whole reason `@stapel/core`'s `putToForeignOrigin` does not
|
|
35
|
+
* appear anywhere in this package. That primitive exists for the docs and
|
|
36
|
+
* recordings contracts, which open a session and hand back a URL at an object
|
|
37
|
+
* store. stapel-cdn takes the bytes itself, through the same authenticated
|
|
38
|
+
* origin as every other call, so the injected client IS the right instrument
|
|
39
|
+
* here and there is no foreign origin to PUT to. Three upload implementations,
|
|
40
|
+
* three contracts (spec §1.6) — the bones core extracted are the ones the
|
|
41
|
+
* OTHER two share.
|
|
42
|
+
*/
|
|
43
|
+
export interface CdnApi {
|
|
44
|
+
readonly client: StapelClient;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Has the CDN already got these bytes?
|
|
48
|
+
*
|
|
49
|
+
* The dedup pre-check, and the pair's only read. Answers `200` either way:
|
|
50
|
+
* `{exists: false, type: null, file: null}` is a successful answer, not an
|
|
51
|
+
* error — "we asked and there is none" (`FileExistsView`).
|
|
52
|
+
*
|
|
53
|
+
* OWNER-SCOPED, ALWAYS. The view filters on `uploaded_by=request.user`
|
|
54
|
+
* unconditionally, unlike the upload paths, which honour
|
|
55
|
+
* `STAPEL_CDN["DEDUP_SCOPE"]`. Two consequences the flow in `model/upload.ts`
|
|
56
|
+
* is built around: (a) a miss here does NOT mean the upload will store new
|
|
57
|
+
* bytes — under `DEDUP_SCOPE: "global"` the POST may still answer "already
|
|
58
|
+
* exists"; (b) a buyer cannot resolve a seller's reference through this
|
|
59
|
+
* endpoint, so it is a re-open-your-own-draft read, never a public one.
|
|
60
|
+
*
|
|
61
|
+
* `IsAuthenticated | IsServiceRequest` — note that this is STRICTER than the
|
|
62
|
+
* upload endpoints, which take `IsNotAnonymousUser` (a guest identity is
|
|
63
|
+
* enough). A guest can therefore upload but not pre-check, which is why a
|
|
64
|
+
* 401 here is a skipped optimisation rather than a failed upload.
|
|
65
|
+
*/
|
|
66
|
+
fileExists(
|
|
67
|
+
fileHash: string,
|
|
68
|
+
options?: { readonly signal?: AbortSignal }
|
|
69
|
+
): Promise<CdnFileExistsResponse>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `POST /upload/image/` — the general image intake (`IsNotAnonymousUser`).
|
|
73
|
+
*
|
|
74
|
+
* Answers `201` for stored bytes and `200` for a server-side dedup hit, with
|
|
75
|
+
* the SAME body either way. The pair does not report which: `StapelClient`
|
|
76
|
+
* resolves a body, not a status, and the only in-body difference is the
|
|
77
|
+
* English `message` string, which is not a contract. Dedup is therefore
|
|
78
|
+
* reported from the pre-check (which is a contract) and from nowhere else —
|
|
79
|
+
* see `model/upload.ts`.
|
|
80
|
+
*
|
|
81
|
+
* The stored row's `type` is `"product"`, hardcoded in the view.
|
|
82
|
+
*/
|
|
83
|
+
uploadImage(
|
|
84
|
+
file: File,
|
|
85
|
+
options?: { readonly signal?: AbortSignal }
|
|
86
|
+
): Promise<CdnImageUploadResponse>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* `POST /upload/avatar/` — the same intake with `type="avatar"`, and the one
|
|
90
|
+
* upload endpoint that requires a real `IsAuthenticated` principal. This is
|
|
91
|
+
* what `profiles-react`'s `useSetAvatar` calls (today through its own
|
|
92
|
+
* documented stopgap, `api/cdnAvatarApi.ts`, which this pair exists to
|
|
93
|
+
* replace).
|
|
94
|
+
*/
|
|
95
|
+
uploadAvatar(
|
|
96
|
+
file: File,
|
|
97
|
+
options?: { readonly signal?: AbortSignal }
|
|
98
|
+
): Promise<CdnImageUploadResponse>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* `POST /images/{image_type}/upload/` — an image stored under a caller-named
|
|
102
|
+
* type, validated against `STAPEL_CDN["ASSET_TYPES"]`.
|
|
103
|
+
*
|
|
104
|
+
* Refuses `error.400.invalid_image_type` for a type this deployment does not
|
|
105
|
+
* declare. Note the asymmetry with {@link uploadImage}, which writes
|
|
106
|
+
* `"product"` without consulting that setting at all: on a default
|
|
107
|
+
* deployment `POST /images/product/upload/` is a 400 while
|
|
108
|
+
* `POST /upload/image/` happily stores a `product` row. A host that wants
|
|
109
|
+
* `product` addressable by name adds it to `ASSET_TYPES`.
|
|
110
|
+
*/
|
|
111
|
+
uploadTypedImage(
|
|
112
|
+
imageType: string,
|
|
113
|
+
file: File,
|
|
114
|
+
options?: { readonly signal?: AbortSignal }
|
|
115
|
+
): Promise<CdnImageUploadResponse>;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* `POST /upload/video/`. Typed and callable; this pair ships no hook and no
|
|
119
|
+
* widget over it (the storefront MVP is images), so a host that needs video
|
|
120
|
+
* today calls it directly rather than waiting for a version of the pair that
|
|
121
|
+
* has the queue for it. Variants are not generated yet upstream — the
|
|
122
|
+
* response's `is_processed` stays false and the ladder is empty.
|
|
123
|
+
*/
|
|
124
|
+
uploadVideo(
|
|
125
|
+
file: File,
|
|
126
|
+
options?: { readonly signal?: AbortSignal }
|
|
127
|
+
): Promise<CdnVideoUploadResponse>;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* `POST /upload/file/` — documents and archives. Same status as
|
|
131
|
+
* {@link uploadVideo}: typed, callable, no hook. Its allowlist is a MIME
|
|
132
|
+
* allowlist as well as an extension one, and deliberately excludes
|
|
133
|
+
* `application/octet-stream`.
|
|
134
|
+
*/
|
|
135
|
+
uploadFile(
|
|
136
|
+
file: File,
|
|
137
|
+
options?: { readonly signal?: AbortSignal }
|
|
138
|
+
): Promise<CdnFileUploadResponse>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The one multipart body every upload endpoint takes: a single `file` part.
|
|
143
|
+
* `FormData` is a `BodyInit`, so `StapelClient` sends it verbatim and lets the
|
|
144
|
+
* browser write the `Content-Type` boundary — setting that header by hand is
|
|
145
|
+
* the classic way to make a multipart POST unparseable server-side.
|
|
146
|
+
*/
|
|
147
|
+
function filePart(file: File): FormData {
|
|
148
|
+
const form = new FormData();
|
|
149
|
+
form.append("file", file);
|
|
150
|
+
return form;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const signalOf = (options?: {
|
|
154
|
+
readonly signal?: AbortSignal;
|
|
155
|
+
}): { signal?: AbortSignal } =>
|
|
156
|
+
options?.signal !== undefined ? { signal: options.signal } : {};
|
|
157
|
+
|
|
158
|
+
export function createCdnApi(client: StapelClient): CdnApi {
|
|
159
|
+
return {
|
|
160
|
+
client,
|
|
161
|
+
|
|
162
|
+
fileExists: (fileHash, options) =>
|
|
163
|
+
client.get("/file/exists/", {
|
|
164
|
+
query: { file_hash: fileHash },
|
|
165
|
+
...signalOf(options),
|
|
166
|
+
}),
|
|
167
|
+
|
|
168
|
+
uploadImage: (file, options) =>
|
|
169
|
+
client.post("/upload/image/", filePart(file), signalOf(options)),
|
|
170
|
+
|
|
171
|
+
uploadAvatar: (file, options) =>
|
|
172
|
+
client.post("/upload/avatar/", filePart(file), signalOf(options)),
|
|
173
|
+
|
|
174
|
+
uploadTypedImage: (imageType, file, options) =>
|
|
175
|
+
client.post(
|
|
176
|
+
`/images/${encodeURIComponent(imageType)}/upload/`,
|
|
177
|
+
filePart(file),
|
|
178
|
+
signalOf(options)
|
|
179
|
+
),
|
|
180
|
+
|
|
181
|
+
uploadVideo: (file, options) =>
|
|
182
|
+
client.post("/upload/video/", filePart(file), signalOf(options)),
|
|
183
|
+
|
|
184
|
+
uploadFile: (file, options) =>
|
|
185
|
+
client.post("/upload/file/", filePart(file), signalOf(options)),
|
|
186
|
+
};
|
|
187
|
+
}
|