@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.
- package/dist/client/admin/components/DocumentForm.js +17 -6
- package/dist/client/richtext/RichTextEditor.d.ts +1 -1
- package/dist/client/richtext/RichTextEditor.d.ts.map +1 -1
- package/dist/client/richtext/RichTextEditor.js +15 -15
- package/dist/client/richtext/components/ImageNodeComponent.js +4 -4
- package/dist/collections/CollectionOperations.d.ts +5 -0
- package/dist/collections/CollectionOperations.d.ts.map +1 -1
- package/dist/collections/CollectionOperations.js +7 -0
- package/dist/collections/operations/create.d.ts.map +1 -1
- package/dist/collections/operations/create.js +20 -0
- package/dist/collections/operations/delete.d.ts.map +1 -1
- package/dist/collections/operations/delete.js +14 -0
- package/dist/collections/operations/drafts.d.ts +33 -0
- package/dist/collections/operations/drafts.d.ts.map +1 -0
- package/dist/collections/operations/drafts.js +43 -0
- package/dist/collections/operations/fieldValidation.d.ts +53 -0
- package/dist/collections/operations/fieldValidation.d.ts.map +1 -0
- package/dist/collections/operations/fieldValidation.js +89 -0
- package/dist/collections/operations/find.d.ts.map +1 -1
- package/dist/collections/operations/find.js +23 -5
- package/dist/collections/operations/findById.d.ts +1 -0
- package/dist/collections/operations/findById.d.ts.map +1 -1
- package/dist/collections/operations/findById.js +33 -21
- package/dist/collections/operations/snapshot.d.ts +30 -0
- package/dist/collections/operations/snapshot.d.ts.map +1 -0
- package/dist/collections/operations/snapshot.js +81 -0
- package/dist/collections/operations/update.d.ts.map +1 -1
- package/dist/collections/operations/update.js +21 -0
- package/dist/error-handling/error-boundary.d.ts.map +1 -1
- package/dist/error-handling/error-boundary.js +11 -7
- package/dist/error-handling/fallback-components.d.ts.map +1 -1
- package/dist/error-handling/fallback-components.js +68 -48
- package/dist/features.d.ts +0 -2
- package/dist/features.d.ts.map +1 -1
- package/dist/features.js +2 -5
- package/dist/generated/types/admin.d.ts +42 -35
- package/dist/generated/types/admin.d.ts.map +1 -1
- package/dist/instance/RevealUIInstance.d.ts.map +1 -1
- package/dist/instance/methods/findById.d.ts +2 -0
- package/dist/instance/methods/findById.d.ts.map +1 -1
- package/dist/license.d.ts +67 -1
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +184 -25
- package/dist/richtext/exports/client/rcc.d.ts.map +1 -1
- package/dist/richtext/exports/client/rcc.js +6 -1
- package/dist/richtext/exports/server/rsc.d.ts +15 -2
- package/dist/richtext/exports/server/rsc.d.ts.map +1 -1
- package/dist/richtext/exports/server/rsc.js +36 -6
- package/dist/storage/index.d.ts +4 -5
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +10 -13
- package/dist/storage/object-storage.d.ts +5 -5
- package/dist/storage/object-storage.js +5 -5
- package/dist/storage/types.d.ts +5 -12
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +4 -4
- package/dist/types/runtime.d.ts +51 -0
- package/dist/types/runtime.d.ts.map +1 -1
- package/package.json +21 -22
- package/dist/storage/vercel-blob-provider.d.ts +0 -28
- package/dist/storage/vercel-blob-provider.d.ts.map +0 -1
- 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
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
//
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
221
|
-
if (!(licenseKey &&
|
|
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,
|
|
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;
|
|
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: {
|
|
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":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,KAAK,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EAAY,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAG3C,YAAY,EAAE,qBAAqB,EAAE,CAAC;AAKtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;
|
|
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
|
|
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;
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -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,
|
|
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)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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":"
|
|
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"}
|
package/dist/storage/index.js
CHANGED
|
@@ -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 (
|
|
7
|
-
//
|
|
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,
|
|
14
|
-
//
|
|
15
|
-
//
|
|
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)
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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', '
|
|
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 —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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 —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -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
|
|
12
|
-
* swap (2026-05-18).
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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", "
|
|
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,
|
|
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"}
|
package/dist/storage/types.js
CHANGED
|
@@ -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
|
|
12
|
-
* swap (2026-05-18).
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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 {};
|