@nitida/asset-client 0.18.1 → 0.19.1

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/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",