@stapel/cdn-react 0.1.0 → 0.2.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 CHANGED
@@ -1 +1,65 @@
1
1
  # @stapel/cdn-react
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 234a091: New pair: `@stapel/cdn-react` — the React half of `stapel-cdn`, and the end of
8
+ `profiles-react/src/api/cdnAvatarApi.ts`'s stated reason to exist.
9
+
10
+ The storefront spec's verdict was that the three upload implementations in this
11
+ fleet are three DIFFERENT contracts, not three copies of one algorithm, and this
12
+ package holds to it: it implements cdn's contract (multipart POST to an
13
+ authenticated origin), while the two genuinely contract-free bones the OTHER two
14
+ share stayed in `@stapel/core` (`putToForeignOrigin`, `useObjectUrlPreview`).
15
+ Only the second appears here — there is no foreign origin in this contract to
16
+ PUT to, and inventing a use for the first would have been the fourth copy in a
17
+ different disguise.
18
+
19
+ What cdn's contract has that the others do not is a content-addressed store with
20
+ a public "do you already have these bytes?" question, so the flow leads with it:
21
+
22
+ ```
23
+ validate ─┬─ refuse (client-side mirror of the deployment's own ceilings)
24
+ └─ hash ── file/exists/ ─┬─ HIT → done, ZERO bytes sent
25
+ └─ MISS → multipart POST → wait for variants
26
+ ```
27
+
28
+ The hit leg is asserted by **counting requests**, which is the only assertion a
29
+ flow that merely looks right cannot pass. And a hit is checked three ways, not
30
+ one: `exists` alone answers about any object with those bytes, so the same file
31
+ stored earlier as a video — or as an avatar rather than a listing photo — is not
32
+ the row the POST would return, and short-circuiting on it would hand a composer
33
+ a reference to the wrong thing.
34
+
35
+ Surface:
36
+
37
+ - `useUploadQueue` / `<MediaUploader max={N}>` — the bag a listing composer
38
+ consumes. `refs` is the ordered `<type>/<hash>` list to store, `reorder` is the
39
+ only way that order moves, and `canAdd` / `settled` are `ActionAvailability`
40
+ so a switched-off Add and a switched-off Save each say which of the reasons
41
+ they are. Per-item cancel (including of an item still waiting for a
42
+ concurrency slot), retry, remove; a reopened draft starts from stored
43
+ references and makes no requests.
44
+ - `useUploadImage` / `<ImageUpload>` — the one-slot shape, built for
45
+ `profiles-react`'s `useSetAvatar` to sit on.
46
+ - `/default` — antd `<MediaGalleryField>` (grid, drag-reorder AND move buttons,
47
+ because drag reaches neither a keyboard nor a phone) and `<ImageUploadField>`.
48
+ - `toStapelImage` — the single place that knows stapel-cdn spells `tier` as an
49
+ int while `@stapel/image` spells it as a string.
50
+ - en inline, `./i18n/ru` and `./i18n/es` opt-in; the 11 error keys stapel-cdn
51
+ owns are authored here because upstream ships no `translations/` at all.
52
+
53
+ Two decisions recorded rather than papered over:
54
+
55
+ **No progress percentage.** `fetch` cannot observe request-body progress through
56
+ the injected client and `SubtleCrypto.digest` reports nothing mid-digest. A bar
57
+ would have meant forking onto `XMLHttpRequest` — a second transport with its own
58
+ auth, refresh and error handling — or animating a number nobody measured. The
59
+ bag names the PHASE instead.
60
+
61
+ **The pre-check is an optimisation and never fails the upload.** `file/exists/`
62
+ is `IsAuthenticated` while the upload endpoints take `IsNotAnonymousUser`, so a
63
+ guest reaches a 401 there and must still be able to post a photo; a page served
64
+ over plain `http://` has no `crypto.subtle` at all. Both fall through to the
65
+ POST and report `dedupSkipped` so a skin can explain it.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stapel contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @stapel/cdn-react 0.1.0
1
+ # @stapel/cdn-react 0.2.0
2
2
 
3
3
  Headless React flow pair for stapel-cdn (contract >=0.12 <0.13) — business + state, zero visual opinion.
4
4
  Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
3
3
  "package": "@stapel/cdn-react",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "backend": {
6
6
  "module": "stapel-cdn",
7
7
  "contract": ">=0.12 <0.13"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/cdn-react",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
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
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,13 +44,6 @@
44
44
  "manifest.json",
45
45
  "llms.txt"
46
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
47
  "size-limit": [
55
48
  {
56
49
  "name": "index — headless pair (client + dedup-first upload flow + queue), no antd",
@@ -94,11 +87,6 @@
94
87
  },
95
88
  "devDependencies": {
96
89
  "@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
90
  "@tanstack/react-query": "^5.81.0",
103
91
  "@testing-library/react": "^16.3.0",
104
92
  "@types/react": "^19.1.0",
@@ -109,12 +97,24 @@
109
97
  "react-dom": "^19.1.0",
110
98
  "size-limit": "^11.2.0",
111
99
  "typescript": "^5.8.3",
112
- "vitest": "^3.2.4"
100
+ "vitest": "^3.2.4",
101
+ "@stapel/core": "^0.15.0",
102
+ "@stapel/image": "^0.2.0",
103
+ "@stapel/showcase": "^0.2.0",
104
+ "@stapel/tokens": "^0.5.0",
105
+ "@stapel/tokens-antd": "^0.5.0"
113
106
  },
114
107
  "engines": {
115
108
  "node": ">=22"
116
109
  },
117
110
  "publishConfig": {
118
111
  "access": "public"
112
+ },
113
+ "scripts": {
114
+ "build": "tsc -p tsconfig.json",
115
+ "test": "tsc -p tsconfig.demo.json && vitest run --exclude test/prodBundlePurity.test.ts",
116
+ "test:pack": "vitest run test/prodBundlePurity.test.ts",
117
+ "lint": "eslint .",
118
+ "size": "size-limit"
119
119
  }
120
- }
120
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-events.mjs — do not edit; drift-gated (pnpm gen:events:check)",
3
3
  "package": "@stapel/cdn-react",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "defined": [],
6
6
  "flows": []
7
7
  }