@revealui/core 0.10.1 → 0.11.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.
Files changed (62) hide show
  1. package/dist/client/admin/components/DocumentForm.js +17 -6
  2. package/dist/client/richtext/RichTextEditor.d.ts +1 -1
  3. package/dist/client/richtext/RichTextEditor.d.ts.map +1 -1
  4. package/dist/client/richtext/RichTextEditor.js +15 -15
  5. package/dist/client/richtext/components/ImageNodeComponent.js +4 -4
  6. package/dist/collections/CollectionOperations.d.ts +5 -0
  7. package/dist/collections/CollectionOperations.d.ts.map +1 -1
  8. package/dist/collections/CollectionOperations.js +7 -0
  9. package/dist/collections/operations/create.d.ts.map +1 -1
  10. package/dist/collections/operations/create.js +20 -0
  11. package/dist/collections/operations/delete.d.ts.map +1 -1
  12. package/dist/collections/operations/delete.js +14 -0
  13. package/dist/collections/operations/drafts.d.ts +33 -0
  14. package/dist/collections/operations/drafts.d.ts.map +1 -0
  15. package/dist/collections/operations/drafts.js +43 -0
  16. package/dist/collections/operations/fieldValidation.d.ts +53 -0
  17. package/dist/collections/operations/fieldValidation.d.ts.map +1 -0
  18. package/dist/collections/operations/fieldValidation.js +89 -0
  19. package/dist/collections/operations/find.d.ts.map +1 -1
  20. package/dist/collections/operations/find.js +23 -5
  21. package/dist/collections/operations/findById.d.ts +1 -0
  22. package/dist/collections/operations/findById.d.ts.map +1 -1
  23. package/dist/collections/operations/findById.js +33 -21
  24. package/dist/collections/operations/snapshot.d.ts +30 -0
  25. package/dist/collections/operations/snapshot.d.ts.map +1 -0
  26. package/dist/collections/operations/snapshot.js +81 -0
  27. package/dist/collections/operations/update.d.ts.map +1 -1
  28. package/dist/collections/operations/update.js +21 -0
  29. package/dist/error-handling/error-boundary.d.ts.map +1 -1
  30. package/dist/error-handling/error-boundary.js +11 -7
  31. package/dist/error-handling/fallback-components.d.ts.map +1 -1
  32. package/dist/error-handling/fallback-components.js +68 -48
  33. package/dist/features.d.ts +0 -2
  34. package/dist/features.d.ts.map +1 -1
  35. package/dist/features.js +2 -5
  36. package/dist/generated/types/admin.d.ts +42 -35
  37. package/dist/generated/types/admin.d.ts.map +1 -1
  38. package/dist/instance/RevealUIInstance.d.ts.map +1 -1
  39. package/dist/instance/methods/findById.d.ts +2 -0
  40. package/dist/instance/methods/findById.d.ts.map +1 -1
  41. package/dist/license.d.ts +67 -1
  42. package/dist/license.d.ts.map +1 -1
  43. package/dist/license.js +184 -25
  44. package/dist/richtext/exports/client/rcc.d.ts.map +1 -1
  45. package/dist/richtext/exports/client/rcc.js +6 -1
  46. package/dist/richtext/exports/server/rsc.d.ts +15 -2
  47. package/dist/richtext/exports/server/rsc.d.ts.map +1 -1
  48. package/dist/richtext/exports/server/rsc.js +36 -6
  49. package/dist/storage/index.d.ts +4 -5
  50. package/dist/storage/index.d.ts.map +1 -1
  51. package/dist/storage/index.js +10 -13
  52. package/dist/storage/object-storage.d.ts +5 -5
  53. package/dist/storage/object-storage.js +5 -5
  54. package/dist/storage/types.d.ts +5 -12
  55. package/dist/storage/types.d.ts.map +1 -1
  56. package/dist/storage/types.js +4 -4
  57. package/dist/types/runtime.d.ts +51 -0
  58. package/dist/types/runtime.d.ts.map +1 -1
  59. package/package.json +21 -22
  60. package/dist/storage/vercel-blob-provider.d.ts +0 -28
  61. package/dist/storage/vercel-blob-provider.d.ts.map +0 -1
  62. package/dist/storage/vercel-blob-provider.js +0 -88
package/dist/license.js CHANGED
@@ -120,14 +120,37 @@ let cachedState = {
120
120
  keyPresentButInvalid: false,
121
121
  };
122
122
  /**
123
- * The public key used to verify license JWTs.
124
- * In production, this is fetched from the license server.
125
- * For local development, it reads from REVEALUI_LICENSE_PUBLIC_KEY env var.
123
+ * Restore real newlines in a PEM stored single-line with literal `\n`
124
+ * (Docker / .env files commonly do this). Fixed-string split/join — no
125
+ * authored regex, per the fleet no-regex rule.
126
126
  */
127
- function getPublicKey() {
128
- const raw = process.env.REVEALUI_LICENSE_PUBLIC_KEY ?? null;
129
- // Docker/env files store PEM as single-line with literal \n - restore real newlines
130
- return raw ? raw.replace(/\\n/g, '\n') : null;
127
+ export function normalizePem(raw) {
128
+ return raw.split('\\n').join('\n');
129
+ }
130
+ /**
131
+ * The ordered public-key candidates used to verify license JWTs.
132
+ *
133
+ * Index 0 is the current key (`REVEALUI_LICENSE_PUBLIC_KEY`); index 1, when
134
+ * present, is the incoming rotation key (`REVEALUI_LICENSE_PUBLIC_KEY_NEXT`).
135
+ * During a zero-downtime key rotation BOTH are configured at once, so a token
136
+ * signed by EITHER key verifies GREEN and no customer is interrupted while
137
+ * re-minted tokens propagate. In production the current key is provisioned
138
+ * from the license server; both read from env here.
139
+ *
140
+ * Exported so the hosted boot canary (`apps/server` validate path) builds the
141
+ * SAME ordered, newline-normalized list the request path uses — a NEXT-signed
142
+ * token must pass the canary during a rotation window exactly as it passes a
143
+ * live request.
144
+ */
145
+ export function getPublicKeys() {
146
+ const keys = [];
147
+ const current = process.env.REVEALUI_LICENSE_PUBLIC_KEY;
148
+ if (current)
149
+ keys.push(normalizePem(current));
150
+ const next = process.env.REVEALUI_LICENSE_PUBLIC_KEY_NEXT;
151
+ if (next)
152
+ keys.push(normalizePem(next));
153
+ return keys;
131
154
  }
132
155
  /**
133
156
  * Reads the license key from environment.
@@ -159,6 +182,16 @@ export async function computeKeyId(publicKeyPem) {
159
182
  * Validates a license key JWT and returns the decoded payload.
160
183
  * Returns null if the key is invalid, expired, or missing.
161
184
  *
185
+ * Multi-key rotation (GAP-259 P0-3): `publicKey` accepts either a single PEM
186
+ * or an ORDERED list of candidate PEMs (current first, then the incoming
187
+ * rotation key). The token is verified against each candidate in turn and is
188
+ * accepted on the FIRST success; it is rejected only when EVERY candidate
189
+ * fails. The JWT `kid` header (a SHA-256 fingerprint via `computeKeyId`) is a
190
+ * perf hint used to try the matching key first — it is NOT authoritative, so a
191
+ * token with no `kid` (or a stale one) still verifies against whichever key
192
+ * actually signed it. This lets a deployment run both keys at once during a
193
+ * zero-downtime rotation. A single string keeps the legacy behavior unchanged.
194
+ *
162
195
  * Phase 1 audit B-2: when `expectedCustomerId` is supplied, the JWT's
163
196
  * `customerId` claim must match exactly — otherwise the token is rejected
164
197
  * even when the signature + iss + aud + exp are all valid. This binds a
@@ -171,25 +204,49 @@ export async function computeKeyId(publicKeyPem) {
171
204
  * options below. Signature is enforced via the public key.
172
205
  */
173
206
  export async function validateLicenseKey(licenseKey, publicKey, expectedCustomerId) {
207
+ const candidates = typeof publicKey === 'string' ? [publicKey] : [...publicKey];
208
+ if (candidates.length === 0)
209
+ return null;
174
210
  try {
175
211
  const jose = await getJose();
176
- // Extract kid from JWT header for forward-compatible key rotation
212
+ // The kid header is a rotation hint: try the key whose fingerprint matches
213
+ // first, then still fall through to the rest (a token may carry no kid).
177
214
  const header = jose.decodeProtectedHeader(licenseKey);
178
- const expectedKid = await computeKeyId(publicKey);
179
- if (header.kid && header.kid !== expectedKid) {
180
- logger.warn(`JWT kid mismatch: token has "${header.kid}", current key is "${expectedKid}". ` +
181
- 'Token may have been signed with a rotated key.');
215
+ const ordered = await orderCandidatesByKid(candidates, header.kid);
216
+ let verifiedPayload = null;
217
+ let verified = false;
218
+ for (const candidate of ordered) {
219
+ try {
220
+ const key = await jose.importSPKI(candidate, 'EdDSA');
221
+ // Accept tokens expired within the subscription grace window so the
222
+ // payload is available for grace-period calculations in isLicensed().
223
+ const { payload } = await jose.jwtVerify(licenseKey, key, {
224
+ algorithms: ['EdDSA'],
225
+ clockTolerance: graceConfig.subscriptionDays * 86_400,
226
+ issuer: LICENSE_ISSUER,
227
+ audience: LICENSE_AUDIENCE,
228
+ });
229
+ verifiedPayload = payload;
230
+ verified = true;
231
+ break;
232
+ }
233
+ catch {
234
+ // This candidate did not verify (wrong key, malformed PEM, bad
235
+ // iss/aud, or expired beyond grace) — try the next before giving up.
236
+ }
182
237
  }
183
- const key = await jose.importSPKI(publicKey, 'EdDSA');
184
- // Accept tokens expired within the subscription grace window so the
185
- // payload is available for grace-period calculations in isLicensed().
186
- const { payload } = await jose.jwtVerify(licenseKey, key, {
187
- algorithms: ['EdDSA'],
188
- clockTolerance: graceConfig.subscriptionDays * 86_400,
189
- issuer: LICENSE_ISSUER,
190
- audience: LICENSE_AUDIENCE,
191
- });
192
- const result = licensePayloadSchema.safeParse(payload);
238
+ if (!verified) {
239
+ // Rejected by every configured key. Warn once (no key material leaked)
240
+ // to aid diagnosing a rotation where the token's signer is not yet — or
241
+ // no longer in REVEALUI_LICENSE_PUBLIC_KEY[_NEXT].
242
+ if (header.kid) {
243
+ logger.warn(`License JWT rejected: token kid "${header.kid}" did not verify against ` +
244
+ `any of the ${candidates.length} configured public key(s). ` +
245
+ 'The signing key may not be in REVEALUI_LICENSE_PUBLIC_KEY / _NEXT.');
246
+ }
247
+ return null;
248
+ }
249
+ const result = licensePayloadSchema.safeParse(verifiedPayload);
193
250
  if (!result.success) {
194
251
  return null;
195
252
  }
@@ -209,6 +266,108 @@ export async function validateLicenseKey(licenseKey, publicKey, expectedCustomer
209
266
  return null;
210
267
  }
211
268
  }
269
+ /**
270
+ * Order candidate PEMs so the one whose `computeKeyId` fingerprint matches the
271
+ * token's `kid` header is tried first. A perf hint only — when `kid` is absent
272
+ * or matches nothing, the original order is preserved and the caller still
273
+ * tries every candidate. Never throws (a malformed candidate simply does not
274
+ * match and is left in place).
275
+ */
276
+ async function orderCandidatesByKid(candidates, kid) {
277
+ if (!kid || candidates.length < 2)
278
+ return candidates;
279
+ for (let i = 0; i < candidates.length; i++) {
280
+ const candidate = candidates[i];
281
+ if (!candidate)
282
+ continue;
283
+ let fingerprint = null;
284
+ try {
285
+ fingerprint = await computeKeyId(candidate);
286
+ }
287
+ catch {
288
+ fingerprint = null;
289
+ }
290
+ if (fingerprint === kid) {
291
+ if (i === 0)
292
+ return candidates;
293
+ return [candidate, ...candidates.slice(0, i), ...candidates.slice(i + 1)];
294
+ }
295
+ }
296
+ return candidates;
297
+ }
298
+ /**
299
+ * Self-test a signing keypair by signing a throwaway token with `privateKey`
300
+ * and verifying it against the ordered `publicKeys` list — the SAME multi-key
301
+ * path real tokens take. Used by the hosted boot canary (GAP-259 P0-4).
302
+ *
303
+ * Distinguishes three outcomes so the caller can react correctly:
304
+ * - `ok` — a token the private key signed verifies against a configured
305
+ * public key, and its kid resolves to a configured key.
306
+ * - `mismatch` — imports all succeeded but NO public key verifies the token:
307
+ * the private key does not pair with any verification key. A
308
+ * definitive fault the caller should fail loud on.
309
+ * - `degraded` — a jose/parse exception (malformed PEM), no public key to
310
+ * verify against, or a kid outside the configured allowlist.
311
+ * Ambiguous / possibly environmental; the caller should alert,
312
+ * not crash.
313
+ *
314
+ * Never throws — every jose exception collapses to `degraded`. The parse of the
315
+ * public PEMs happens up front precisely so a malformed public key is reported
316
+ * as `degraded` rather than masquerading as a `mismatch` (validateLicenseKey
317
+ * collapses import failures and signature failures alike to null). Edge-compatible.
318
+ */
319
+ export async function selfVerifyLicenseKeypair(privateKey, publicKeys) {
320
+ if (publicKeys.length === 0) {
321
+ return { status: 'degraded', reason: 'no public key configured to verify against' };
322
+ }
323
+ const jose = await getJose();
324
+ // Pre-parse every public PEM so a MALFORMED public key surfaces as `degraded`
325
+ // (a jose import exception) instead of an indistinguishable `mismatch`.
326
+ for (const pk of publicKeys) {
327
+ try {
328
+ await jose.importSPKI(pk, 'EdDSA');
329
+ }
330
+ catch (err) {
331
+ return {
332
+ status: 'degraded',
333
+ reason: `public key import failed: ${err instanceof Error ? err.message : String(err)}`,
334
+ };
335
+ }
336
+ }
337
+ // Sign a throwaway token with the deployment's own private key. A malformed
338
+ // private PEM throws inside generateLicenseKey (importPKCS8) → degraded.
339
+ const firstKey = publicKeys[0];
340
+ let token;
341
+ try {
342
+ token = await generateLicenseKey({ tier: 'pro', customerId: 'canary' }, privateKey, 60, firstKey);
343
+ }
344
+ catch (err) {
345
+ return {
346
+ status: 'degraded',
347
+ reason: `canary token signing failed: ${err instanceof Error ? err.message : String(err)}`,
348
+ };
349
+ }
350
+ // Verify against the ordered public-key list. A null result AFTER imports
351
+ // succeeded means no configured key verifies a token our own private key
352
+ // signed: a definitive keypair mismatch.
353
+ const payload = await validateLicenseKey(token, publicKeys);
354
+ if (payload === null) {
355
+ return { status: 'mismatch' };
356
+ }
357
+ // kid allowlist: the token's kid must resolve to a configured public key.
358
+ const header = jose.decodeProtectedHeader(token);
359
+ const kid = typeof header.kid === 'string' ? header.kid : undefined;
360
+ if (kid !== undefined) {
361
+ const allowed = await Promise.all(publicKeys.map((pk) => computeKeyId(pk)));
362
+ if (!allowed.includes(kid)) {
363
+ return {
364
+ status: 'degraded',
365
+ reason: `signed token kid "${kid}" is not among the configured public-key ids [${allowed.join(', ')}]`,
366
+ };
367
+ }
368
+ }
369
+ return { status: 'ok', kid };
370
+ }
212
371
  /**
213
372
  * Initialize the license system. Call once at application startup.
214
373
  * Reads REVEALUI_LICENSE_KEY and REVEALUI_LICENSE_PUBLIC_KEY from environment.
@@ -217,8 +376,8 @@ export async function validateLicenseKey(licenseKey, publicKey, expectedCustomer
217
376
  */
218
377
  export async function initializeLicense() {
219
378
  const licenseKey = await getLicenseKey();
220
- const publicKey = getPublicKey();
221
- if (!(licenseKey && publicKey)) {
379
+ const publicKeys = getPublicKeys();
380
+ if (!(licenseKey && publicKeys.length > 0)) {
222
381
  cachedState = {
223
382
  tier: 'free',
224
383
  payload: null,
@@ -228,7 +387,7 @@ export async function initializeLicense() {
228
387
  cachedAt = Date.now();
229
388
  return 'free';
230
389
  }
231
- const payload = await validateLicenseKey(licenseKey, publicKey);
390
+ const payload = await validateLicenseKey(licenseKey, publicKeys);
232
391
  if (!payload) {
233
392
  // Key was present but failed validation (expired beyond grace, invalid signature, etc.)
234
393
  cachedState = {
@@ -1 +1 @@
1
- {"version":3,"file":"rcc.d.ts","sourceRoot":"","sources":["../../../../src/richtext/exports/client/rcc.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAA0B,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIhF,OAAO,EAQL,KAAK,qBAAqB,EAE1B,KAAK,qBAAqB,EAE3B,MAAM,SAAS,CAAC;AACjB,OAAO,EAAY,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AA2B3C,UAAU,mBAAoB,SAAQ,qBAAqB;IACzD,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;IAC/E,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,kBAAkB,EACxB,QAAQ,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,EAC5B,KAAK,EAAE,MAAM,KACV,GAAG,CAAC,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,CACZ,IAAI,EAAE;QACJ,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,EACD,KAAK,EAAE,MAAM,KACV,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;CACzB;AA0PD;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,EAC9C,OAAO,CAAC,EAAE,sBAAsB,GAC/B,GAAG,CAAC,OAAO,GAAG,IAAI,CAMpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,SAAS,EACT,GAAG,OAAO,EACX,EAAE;IACD,IAAI,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,sBAAsB,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAO9C"}
1
+ {"version":3,"file":"rcc.d.ts","sourceRoot":"","sources":["../../../../src/richtext/exports/client/rcc.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAA0B,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIhF,OAAO,EAQL,KAAK,qBAAqB,EAE1B,KAAK,qBAAqB,EAE3B,MAAM,SAAS,CAAC;AACjB,OAAO,EAAY,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AA2B3C,UAAU,mBAAoB,SAAQ,qBAAqB;IACzD,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;IAC/E,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,kBAAkB,EACxB,QAAQ,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,EAC5B,KAAK,EAAE,MAAM,KACV,GAAG,CAAC,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,CACZ,IAAI,EAAE;QACJ,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,EACD,KAAK,EAAE,MAAM,KACV,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC;CACzB;AA+PD;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,EAC9C,OAAO,CAAC,EAAE,sBAAsB,GAC/B,GAAG,CAAC,OAAO,GAAG,IAAI,CAMpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,SAAS,EACT,GAAG,OAAO,EACX,EAAE;IACD,IAAI,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,sBAAsB,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAO9C"}
@@ -114,7 +114,12 @@ function serializeNode(node, index, options) {
114
114
  if (options?.renderImage) {
115
115
  return options.renderImage({ src, alt, width, height, caption }, index);
116
116
  }
117
- return (_jsxs("figure", { style: { margin: '16px 0', textAlign: 'center' }, children: [_jsx("img", { src: src, alt: alt, width: width, height: height, style: { maxWidth: '100%', height: 'auto', display: 'block' } }), caption && (_jsx("figcaption", { style: { marginTop: '8px', fontSize: '14px', color: '#64748b', fontStyle: 'italic' }, children: caption }))] }, index));
117
+ return (_jsxs("figure", { style: { margin: '16px 0', textAlign: 'center' }, children: [_jsx("img", { src: src, alt: alt, width: width, height: height, style: { maxWidth: '100%', height: 'auto', display: 'block' } }), caption && (_jsx("figcaption", { style: {
118
+ marginTop: '8px',
119
+ fontSize: '14px',
120
+ color: 'var(--rvui-text-2, #64748b)',
121
+ fontStyle: 'italic',
122
+ }, children: caption }))] }, index));
118
123
  }
119
124
  // Horizontal rule
120
125
  if (node.type === 'horizontalrule') {
@@ -66,7 +66,7 @@ interface SerializeOptions {
66
66
  renderBlock?: (node: SerializedBlockNode, index: number) => JSX.Element | null;
67
67
  /** Custom link renderer */
68
68
  renderLink?: (node: SerializedLinkNode, children: JSX.Element | null, index: number) => JSX.Element;
69
- /** Custom image renderer */
69
+ /** Custom image renderer. `data.src` arrives already sanitized. */
70
70
  renderImage?: (data: {
71
71
  src?: string;
72
72
  alt?: string;
@@ -74,6 +74,13 @@ interface SerializeOptions {
74
74
  height?: number;
75
75
  caption?: string;
76
76
  }, index: number) => JSX.Element | null;
77
+ /** Custom upload-node renderer. `data.url` arrives already sanitized. */
78
+ renderUpload?: (data: {
79
+ url?: string;
80
+ alt?: string;
81
+ width?: number;
82
+ height?: number;
83
+ }, index: number) => JSX.Element | null;
77
84
  }
78
85
  /**
79
86
  * Serialize a Lexical editor state to React elements
@@ -113,7 +120,13 @@ export interface RichTextContentProps {
113
120
  height?: number;
114
121
  caption?: string;
115
122
  }, index: number) => JSX.Element | null;
123
+ renderUpload?: (data: {
124
+ url?: string;
125
+ alt?: string;
126
+ width?: number;
127
+ height?: number;
128
+ }, index: number) => JSX.Element | null;
116
129
  }
117
- export declare function RichTextContent({ data, className, renderBlock, renderLink, renderImage, }: RichTextContentProps): JSX.Element | null;
130
+ export declare function RichTextContent({ data, className, renderBlock, renderLink, renderImage, renderUpload, }: RichTextContentProps): JSX.Element | null;
118
131
  export default RichTextContent;
119
132
  //# sourceMappingURL=rsc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../../src/richtext/exports/server/rsc.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../../src/richtext/exports/server/rsc.tsx"],"names":[],"mappings":"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"}
@@ -26,6 +26,11 @@ const IS_UNDERLINE = 8;
26
26
  const IS_CODE = 16;
27
27
  const IS_SUBSCRIPT = 32;
28
28
  const IS_SUPERSCRIPT = 64;
29
+ // Heading `tag` values come from stored, author-controlled Lexical JSON and
30
+ // are rendered as JSX element names — allow-list them (a forged tag like
31
+ // "script" would otherwise render a live <script> element, whose text
32
+ // children React does not escape).
33
+ const HEADING_TAGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
29
34
  /**
30
35
  * Serialize a text node with formatting
31
36
  */
@@ -87,9 +92,9 @@ function serializeNode(node, index, options) {
87
92
  if (node.type === 'paragraph') {
88
93
  return _jsx("p", { children: serializeChildren(n.children, options) }, index);
89
94
  }
90
- // Heading
95
+ // Heading — the stored tag is author-controlled; render only allow-listed h1-h6
91
96
  if (node.type === 'heading') {
92
- const Tag = (n.tag || 'h1');
97
+ const Tag = (n.tag && HEADING_TAGS.has(n.tag) ? n.tag : 'h1');
93
98
  return _jsx(Tag, { children: serializeChildren(n.children, options) }, index);
94
99
  }
95
100
  // Quote
@@ -141,9 +146,9 @@ function serializeNode(node, index, options) {
141
146
  const width = imageNode.fields?.width;
142
147
  const height = imageNode.fields?.height;
143
148
  const caption = imageNode.fields?.caption;
144
- // Allow custom image renderer
149
+ // Allow custom image renderer — hand it the sanitized src, never the raw one
145
150
  if (options?.renderImage) {
146
- return options.renderImage(imageNode.fields || {}, index);
151
+ return options.renderImage({ ...(imageNode.fields || {}), src }, index);
147
152
  }
148
153
  // Default image rendering
149
154
  return (_jsxs("figure", { style: { margin: '16px 0', textAlign: 'center' }, children: [_jsx("img", { src: src, alt: alt, width: width, height: height, style: {
@@ -153,10 +158,34 @@ function serializeNode(node, index, options) {
153
158
  } }), caption && (_jsx("figcaption", { style: {
154
159
  marginTop: '8px',
155
160
  fontSize: '14px',
156
- color: '#64748b',
161
+ color: 'var(--rvui-text-2, #64748b)',
157
162
  fontStyle: 'italic',
158
163
  }, children: caption }))] }, index));
159
164
  }
165
+ // Upload (media-collection reference from the rich-text upload feature).
166
+ // The stored value.url is author-controlled; sanitize before any renderer
167
+ // sees it.
168
+ if (node.type === 'upload') {
169
+ const uploadNode = node;
170
+ const value = uploadNode.value;
171
+ if (!value || typeof value === 'string' || !value.url) {
172
+ return null;
173
+ }
174
+ const url = sanitizeUrl(value.url, 'image');
175
+ if (options?.renderUpload) {
176
+ return options.renderUpload({ url, alt: value.alt, width: value.width, height: value.height }, index);
177
+ }
178
+ return (_jsxs("figure", { style: { margin: '16px 0' }, children: [_jsx("img", { src: url, alt: value.alt || '', width: value.width, height: value.height, style: {
179
+ maxWidth: '100%',
180
+ height: 'auto',
181
+ display: 'block',
182
+ } }), value.alt && (_jsx("figcaption", { style: {
183
+ marginTop: '8px',
184
+ fontSize: '14px',
185
+ color: 'var(--rvui-text-2, #64748b)',
186
+ fontStyle: 'italic',
187
+ }, children: value.alt }))] }, index));
188
+ }
160
189
  // Horizontal rule
161
190
  if (node.type === 'horizontalrule') {
162
191
  return _jsx("hr", {}, index);
@@ -287,7 +316,7 @@ export function RscEntryLexicalField({ data, className }) {
287
316
  }
288
317
  return _jsx("div", { className: className, children: content });
289
318
  }
290
- export function RichTextContent({ data, className, renderBlock, renderLink, renderImage, }) {
319
+ export function RichTextContent({ data, className, renderBlock, renderLink, renderImage, renderUpload, }) {
291
320
  if (!data) {
292
321
  return null;
293
322
  }
@@ -295,6 +324,7 @@ export function RichTextContent({ data, className, renderBlock, renderLink, rend
295
324
  renderBlock: renderBlock || undefined,
296
325
  renderLink: renderLink || undefined,
297
326
  renderImage: renderImage || undefined,
327
+ renderUpload: renderUpload || undefined,
298
328
  });
299
329
  if (!content) {
300
330
  return null;
@@ -3,17 +3,16 @@ export { createMockProvider } from './mock.js';
3
3
  export type { ObjectStorageConfig } from './object-storage.js';
4
4
  export { objectStorage } from './object-storage.js';
5
5
  export { createR2Provider } from './r2.js';
6
- export type { ListItem, ListOptions, ListResult, PutOptions, PutResult, R2Config, StorageConfig, StorageProvider, VercelBlobConfig, } from './types.js';
7
- export { createVercelBlobProvider } from './vercel-blob-provider.js';
6
+ export type { ListItem, ListOptions, ListResult, PutOptions, PutResult, R2Config, StorageConfig, StorageProvider, } from './types.js';
8
7
  /**
9
8
  * Construct a StorageProvider from a tagged config.
10
9
  *
11
10
  * Per `feedback_pluggable_provider_pattern`: explicit "no-X" opt-in — unknown
12
11
  * tags throw with a clear message; no silent fallback.
13
12
  *
14
- * Supported tags: 'r2' (canonical), 'vercel-blob' (legacy migration-window
15
- * fallback), and 'mock' (tests). The provider-agnostic `objectStorage` Payload
16
- * plugin (object-storage.ts) adapts any of these to apps/admin's upload path.
13
+ * Supported tags: 'r2' (canonical) and 'mock' (tests). The provider-agnostic
14
+ * `objectStorage` Payload plugin (object-storage.ts) adapts either to apps/admin's
15
+ * upload path.
17
16
  */
18
17
  export declare function createStorage(config: StorageConfig): StorageProvider;
19
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAK/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,YAAY,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACT,QAAQ,EACR,aAAa,EACb,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CAgBpE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAK/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,YAAY,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACT,QAAQ,EACR,aAAa,EACb,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CAcpE"}
@@ -3,44 +3,41 @@
3
3
  //
4
4
  // Phase 1 (#959): types.ts + index.ts type re-exports.
5
5
  // Phase 2a (#959 follow-up): r2 + mock providers + createStorage factory.
6
- // Phase 2b (this PR): vercel-blob StorageProvider + apps/server media-route
7
- // cutover. R2 is canonical; Vercel Blob is the migration-window fallback.
6
+ // Phase 2b (#959 follow-up): apps/server media-route cutover onto R2.
7
+ // #1644: legacy Vercel Blob StorageProvider removed once R2 was confirmed in
8
+ // every production environment — R2 is now the sole non-mock backend.
8
9
  import { createMockProvider } from './mock.js';
9
10
  import { createR2Provider } from './r2.js';
10
- import { createVercelBlobProvider } from './vercel-blob-provider.js';
11
11
  export { createMockProvider } from './mock.js';
12
12
  // Provider-agnostic Payload-style upload plugin — adapts any StorageProvider
13
- // (R2 canonical, Vercel Blob fallback, mock) to the engine's collection upload
14
- // adapter. Used by apps/admin/revealui.config.ts. GAP-208 Phase 4 replaced the
15
- // provider-specific `vercelBlobStorage` plugin with this.
13
+ // (R2 canonical, mock) to the engine's collection upload adapter. Used by
14
+ // apps/admin/revealui.config.ts. GAP-208 Phase 4 replaced the provider-specific
15
+ // `vercelBlobStorage` plugin with this.
16
16
  export { objectStorage } from './object-storage.js';
17
17
  // Provider factories — exported so callers can construct a provider directly
18
18
  // when they already know which one they want (skipping the discriminated-union
19
19
  // factory below).
20
20
  export { createR2Provider } from './r2.js';
21
- export { createVercelBlobProvider } from './vercel-blob-provider.js';
22
21
  /**
23
22
  * Construct a StorageProvider from a tagged config.
24
23
  *
25
24
  * Per `feedback_pluggable_provider_pattern`: explicit "no-X" opt-in — unknown
26
25
  * tags throw with a clear message; no silent fallback.
27
26
  *
28
- * Supported tags: 'r2' (canonical), 'vercel-blob' (legacy migration-window
29
- * fallback), and 'mock' (tests). The provider-agnostic `objectStorage` Payload
30
- * plugin (object-storage.ts) adapts any of these to apps/admin's upload path.
27
+ * Supported tags: 'r2' (canonical) and 'mock' (tests). The provider-agnostic
28
+ * `objectStorage` Payload plugin (object-storage.ts) adapts either to apps/admin's
29
+ * upload path.
31
30
  */
32
31
  export function createStorage(config) {
33
32
  switch (config.provider) {
34
33
  case 'r2':
35
34
  return createR2Provider(config.r2);
36
- case 'vercel-blob':
37
- return createVercelBlobProvider(config.vercelBlob);
38
35
  case 'mock':
39
36
  return createMockProvider();
40
37
  default: {
41
38
  const _exhaustive = config;
42
39
  throw new Error(`createStorage: unknown provider tag. Got ${JSON.stringify(_exhaustive)}. ` +
43
- "Valid tags: 'r2', 'vercel-blob', 'mock'.");
40
+ "Valid tags: 'r2', 'mock'.");
44
41
  }
45
42
  }
46
43
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Object-storage upload plugin (provider-agnostic).
3
3
  *
4
- * Adapts any `StorageProvider` (Cloudflare R2 — canonical — Vercel Blob, mock,
5
- * or a future backend) to the RevealUI engine's collection upload-adapter
6
- * interface, so `apps/admin/revealui.config.ts` stores media through the
7
- * configured object-storage backend. Replaces the provider-specific
8
- * `vercelBlobStorage` Payload plugin retired in GAP-208 Phase 4.
4
+ * Adapts any `StorageProvider` (Cloudflare R2 — canonical — mock, or a future
5
+ * backend) to the RevealUI engine's collection upload-adapter interface, so
6
+ * `apps/admin/revealui.config.ts` stores media through the configured
7
+ * object-storage backend. Replaces the provider-specific `vercelBlobStorage`
8
+ * Payload plugin retired in GAP-208 Phase 4.
9
9
  *
10
10
  * The backend is resolved through a lazy `resolveProvider` thunk — invoked
11
11
  * (and memoized) on the first upload/delete, never at config-build time — so a
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Object-storage upload plugin (provider-agnostic).
3
3
  *
4
- * Adapts any `StorageProvider` (Cloudflare R2 — canonical — Vercel Blob, mock,
5
- * or a future backend) to the RevealUI engine's collection upload-adapter
6
- * interface, so `apps/admin/revealui.config.ts` stores media through the
7
- * configured object-storage backend. Replaces the provider-specific
8
- * `vercelBlobStorage` Payload plugin retired in GAP-208 Phase 4.
4
+ * Adapts any `StorageProvider` (Cloudflare R2 — canonical — mock, or a future
5
+ * backend) to the RevealUI engine's collection upload-adapter interface, so
6
+ * `apps/admin/revealui.config.ts` stores media through the configured
7
+ * object-storage backend. Replaces the provider-specific `vercelBlobStorage`
8
+ * Payload plugin retired in GAP-208 Phase 4.
9
9
  *
10
10
  * The backend is resolved through a lazy `resolveProvider` thunk — invoked
11
11
  * (and memoized) on the first upload/delete, never at config-build time — so a
@@ -8,10 +8,10 @@
8
8
  * apps/admin health probes, packages/core Payload-style plugin shims) depend
9
9
  * only on this contract. Switching providers becomes a config change.
10
10
  *
11
- * GAP-208 introduces this abstraction during the Vercel Blob → Cloudflare R2
12
- * swap (2026-05-18). The vercel-blob Payload-style plugin (vercel-blob.ts)
13
- * stays in place during migration for backward compat; once consumers migrate
14
- * to the interface, the legacy plugin is dropped.
11
+ * GAP-208 introduced this abstraction during the Vercel Blob → Cloudflare R2
12
+ * swap (2026-05-18). #1644 removed the Vercel Blob StorageProvider once R2 was
13
+ * confirmed in every production environment; Cloudflare R2 is now the sole
14
+ * non-mock backend.
15
15
  */
16
16
  export interface StorageProvider {
17
17
  /** Upload binary data. Returns a fully-qualified URL the value is reachable at. */
@@ -20,7 +20,7 @@ export interface StorageProvider {
20
20
  del(keyOrUrl: string): Promise<void>;
21
21
  /** List items, optionally filtered by key prefix. Used by health probes + admin browsing. */
22
22
  list(opts?: ListOptions): Promise<ListResult>;
23
- /** Provider tag (e.g. "r2", "vercel-blob", "mock") — exposed so consumers can adapt URL handling. */
23
+ /** Provider tag (e.g. "r2", "mock") — exposed so consumers can adapt URL handling. */
24
24
  readonly provider: string;
25
25
  }
26
26
  export interface PutOptions {
@@ -80,9 +80,6 @@ export interface ListItem {
80
80
  export type StorageConfig = {
81
81
  provider: 'r2';
82
82
  r2: R2Config;
83
- } | {
84
- provider: 'vercel-blob';
85
- vercelBlob: VercelBlobConfig;
86
83
  } | {
87
84
  provider: 'mock';
88
85
  };
@@ -103,8 +100,4 @@ export interface R2Config {
103
100
  */
104
101
  publicBaseUrl?: string;
105
102
  }
106
- export interface VercelBlobConfig {
107
- /** vercel_blob_rw_* token from the Vercel Blob dashboard. */
108
- token: string;
109
- }
110
103
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,GAAG,CACD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,EAClE,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,SAAS,CAAC,CAAC;IAEtB,8FAA8F;IAC9F,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErC,6FAA6F;IAC7F,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAE9C,qGAAqG;IACrG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAID,MAAM,WAAW,UAAU;IACzB,uGAAuG;IACvG,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAE9B,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,GAAG,EAAE,MAAM,CAAC;IAEZ,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IAEZ,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IAEb,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAElB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,IAAI,CAAC;CAClB;AAID;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAA;CAAE,GAChC;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,gBAAgB,CAAA;CAAE,GACzD;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzB,MAAM,WAAW,QAAQ;IACvB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAElB,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IAEpB,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC;IAExB,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;CACf"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,GAAG,CACD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,EAClE,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,SAAS,CAAC,CAAC;IAEtB,8FAA8F;IAC9F,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErC,6FAA6F;IAC7F,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAE9C,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAID,MAAM,WAAW,UAAU;IACzB,uGAAuG;IACvG,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAE9B,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,GAAG,EAAE,MAAM,CAAC;IAEZ,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IAEZ,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IAEb,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAElB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,IAAI,CAAC;CAClB;AAID;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpF,MAAM,WAAW,QAAQ;IACvB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAElB,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IAEpB,gEAAgE;IAChE,eAAe,EAAE,MAAM,CAAC;IAExB,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
@@ -8,9 +8,9 @@
8
8
  * apps/admin health probes, packages/core Payload-style plugin shims) depend
9
9
  * only on this contract. Switching providers becomes a config change.
10
10
  *
11
- * GAP-208 introduces this abstraction during the Vercel Blob → Cloudflare R2
12
- * swap (2026-05-18). The vercel-blob Payload-style plugin (vercel-blob.ts)
13
- * stays in place during migration for backward compat; once consumers migrate
14
- * to the interface, the legacy plugin is dropped.
11
+ * GAP-208 introduced this abstraction during the Vercel Blob → Cloudflare R2
12
+ * swap (2026-05-18). #1644 removed the Vercel Blob StorageProvider once R2 was
13
+ * confirmed in every production environment; Cloudflare R2 is now the sole
14
+ * non-mock backend.
15
15
  */
16
16
  export {};