@nitida/asset-client 0.18.0 → 0.19.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/AGENTS.md +16 -0
- package/dist/index.cjs +50 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +45 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access.ts +54 -2
- package/src/index.ts +72 -0
- package/src/transform.ts +6 -1
package/src/index.ts
CHANGED
|
@@ -80,6 +80,67 @@ export type RequestablePreset =
|
|
|
80
80
|
// compact `presets` wire string — so it is here and not on VariantPreset.
|
|
81
81
|
| "probe";
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* The same set as {@link RequestablePreset}, at RUNTIME.
|
|
85
|
+
*
|
|
86
|
+
* The type stops the mistake in TypeScript. It cannot stop it anywhere else,
|
|
87
|
+
* and "anywhere else" is where it keeps happening: a preset list assembled
|
|
88
|
+
* from config, from a route body, from JSON, or from a script's argv arrives
|
|
89
|
+
* as `string[]`, and the only way past the type was a cast.
|
|
90
|
+
*
|
|
91
|
+
* Measured in neo-real-estate on 2026-08-23, in THREE independent files:
|
|
92
|
+
*
|
|
93
|
+
* presets: [...opts.presets] as VariantPreset[]
|
|
94
|
+
*
|
|
95
|
+
* — a blind cast, and to the wrong type at that: `VariantPreset` includes
|
|
96
|
+
* `hls` and `mp3`, which are precisely the two you may not ask for. Every one
|
|
97
|
+
* of those casts would have compiled a request the server answers 400.
|
|
98
|
+
*
|
|
99
|
+
* So the narrowing lives here, once, instead of being re-invented per repo.
|
|
100
|
+
*/
|
|
101
|
+
export const REQUESTABLE_PRESETS: readonly RequestablePreset[] = [
|
|
102
|
+
"thumb",
|
|
103
|
+
"sm",
|
|
104
|
+
"md",
|
|
105
|
+
"lg",
|
|
106
|
+
"xl",
|
|
107
|
+
"original",
|
|
108
|
+
"poster",
|
|
109
|
+
"video",
|
|
110
|
+
"aiproxy",
|
|
111
|
+
"probe",
|
|
112
|
+
] as const;
|
|
113
|
+
|
|
114
|
+
/** Type guard for a single value. */
|
|
115
|
+
export const isRequestablePreset = (v: string): v is RequestablePreset =>
|
|
116
|
+
(REQUESTABLE_PRESETS as readonly string[]).includes(v);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Narrow a `string[]` to the presets the server can actually produce, or throw
|
|
120
|
+
* naming the offender.
|
|
121
|
+
*
|
|
122
|
+
* Throws rather than filtering silently, for the same reason the seven URL
|
|
123
|
+
* builders throw on a private asset: **a silence reads as "you can't"**. A
|
|
124
|
+
* caller that asked for `hls` wants an HLS ladder; dropping it quietly returns
|
|
125
|
+
* a 200 and no ladder, and the 404 lands later and somewhere else. The error
|
|
126
|
+
* names the value, says why the server cannot make it, and lists what it can.
|
|
127
|
+
*/
|
|
128
|
+
export const toRequestablePresets = (
|
|
129
|
+
input: readonly string[],
|
|
130
|
+
): RequestablePreset[] => {
|
|
131
|
+
const bad = input.filter((p) => !isRequestablePreset(p));
|
|
132
|
+
if (bad.length > 0) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Cannot request ${bad.map((b) => `"${b}"`).join(", ")}. ` +
|
|
135
|
+
"`hls` and `mp3` are things a variant can BE, not things you may ask " +
|
|
136
|
+
"for — the server derives them itself (hls when a video is " +
|
|
137
|
+
"transcoded, mp3 alongside any audio original). " +
|
|
138
|
+
`Requestable: ${REQUESTABLE_PRESETS.join(", ")}.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return input as RequestablePreset[];
|
|
142
|
+
};
|
|
143
|
+
|
|
83
144
|
/** 1-char alias used in storage keys / wire `presets` string. */
|
|
84
145
|
export const PRESET_SHORT: Record<VariantPreset, string> = {
|
|
85
146
|
thumb: "q",
|
|
@@ -374,6 +435,7 @@ export type AssetDTO = {
|
|
|
374
435
|
export {
|
|
375
436
|
accessMessage,
|
|
376
437
|
assertPublic,
|
|
438
|
+
assertSha,
|
|
377
439
|
deriveAccessKey,
|
|
378
440
|
type SignAccessOptions,
|
|
379
441
|
signAccessUrl,
|
|
@@ -398,6 +460,7 @@ export {
|
|
|
398
460
|
|
|
399
461
|
import {
|
|
400
462
|
assertPublic,
|
|
463
|
+
assertSha,
|
|
401
464
|
type SignAccessOptions,
|
|
402
465
|
signAccessUrl,
|
|
403
466
|
type VisibilityHint,
|
|
@@ -556,6 +619,7 @@ export function getAssetUrl(
|
|
|
556
619
|
preset: VariantPreset,
|
|
557
620
|
): string {
|
|
558
621
|
// Refuses rather than returning a URL that 404s. See `assertPublic`.
|
|
622
|
+
assertSha(asset, "getAssetUrl");
|
|
559
623
|
assertPublic(
|
|
560
624
|
asset,
|
|
561
625
|
"getAssetUrl",
|
|
@@ -626,6 +690,13 @@ function buildPublicAssetUrl(
|
|
|
626
690
|
asset: Pick<AssetDTO, "sha"> & OriginalHints,
|
|
627
691
|
preset: VariantPreset,
|
|
628
692
|
): string {
|
|
693
|
+
// The guard lives HERE and not only in `getAssetUrl`, because
|
|
694
|
+
// `getPrivateAssetUrl` reaches this function directly. Without it, passing an
|
|
695
|
+
// upload result produced `/a/5/v/undefined-l.webp?exp=…&sig=…` — a URL with a
|
|
696
|
+
// **cryptographically valid signature over a path containing `undefined`**.
|
|
697
|
+
// That is strictly worse than the public case: the signature makes it look
|
|
698
|
+
// authoritative, and it passes shape checks at the edge before 404ing.
|
|
699
|
+
assertSha(asset, "getPrivateAssetUrl");
|
|
629
700
|
if (preset === "original") {
|
|
630
701
|
// The stored URL beats every derivation, because it IS the key. Only fall
|
|
631
702
|
// through to a guess when the caller gave us the sha and nothing else.
|
|
@@ -818,6 +889,7 @@ export function getAssetSrcSet(
|
|
|
818
889
|
// Doing this properly means capping the rungs by the asset's real width, and
|
|
819
890
|
// this signature does not carry it (`Pick<AssetDTO,"sha"|"presets">`). Worth
|
|
820
891
|
// doing; not worth guessing.
|
|
892
|
+
assertSha(asset, "getAssetSrcSet");
|
|
821
893
|
assertPublic(
|
|
822
894
|
asset,
|
|
823
895
|
"getAssetSrcSet",
|
package/src/transform.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* request's Accept header.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { assertPublic, type VisibilityHint } from "./access";
|
|
21
|
+
import { assertPublic, assertSha, type VisibilityHint } from "./access";
|
|
22
22
|
import type { AssetDTO } from "./index";
|
|
23
23
|
import { getCdnBase } from "./index";
|
|
24
24
|
|
|
@@ -231,6 +231,7 @@ export function getVideoTransformUrl(
|
|
|
231
231
|
asset: Pick<AssetDTO, "sha"> & VisibilityHint,
|
|
232
232
|
opts: TransformOptions,
|
|
233
233
|
): string | null {
|
|
234
|
+
assertSha(asset, "getVideoTransformUrl");
|
|
234
235
|
assertPublic(
|
|
235
236
|
asset,
|
|
236
237
|
"getVideoTransformUrl",
|
|
@@ -304,6 +305,7 @@ export function getHlsStreamingUrl(
|
|
|
304
305
|
asset: Pick<AssetDTO, "sha"> & VisibilityHint,
|
|
305
306
|
opts: Omit<TransformOptions, "format"> = {},
|
|
306
307
|
): string {
|
|
308
|
+
assertSha(asset, "getHlsStreamingUrl");
|
|
307
309
|
assertPublic(
|
|
308
310
|
asset,
|
|
309
311
|
"getHlsStreamingUrl",
|
|
@@ -336,6 +338,7 @@ export function getTransformUrl(
|
|
|
336
338
|
asset: Pick<AssetDTO, "sha"> & VisibilityHint,
|
|
337
339
|
opts: TransformOptions,
|
|
338
340
|
): string | null {
|
|
341
|
+
assertSha(asset, "getTransformUrl");
|
|
339
342
|
assertPublic(
|
|
340
343
|
asset,
|
|
341
344
|
"getTransformUrl",
|
|
@@ -366,6 +369,7 @@ export function getSignedTransformUrl(
|
|
|
366
369
|
// where a backend developer holding a signing key and a private asset ends
|
|
367
370
|
// up — so without this, the same call site throws when unsigned and returns
|
|
368
371
|
// a doomed URL when signed, which is the worst of both.
|
|
372
|
+
assertSha(asset, "getSignedTransformUrl");
|
|
369
373
|
assertPublic(
|
|
370
374
|
asset,
|
|
371
375
|
"getSignedTransformUrl",
|
|
@@ -438,6 +442,7 @@ export function getTransformSrcSet(
|
|
|
438
442
|
widths: number[],
|
|
439
443
|
extraOpts: Omit<TransformOptions, "width"> = {},
|
|
440
444
|
): string {
|
|
445
|
+
assertSha(asset, "getTransformSrcSet");
|
|
441
446
|
assertPublic(
|
|
442
447
|
asset,
|
|
443
448
|
"getTransformSrcSet",
|