@revealui/core 0.8.0 → 0.9.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/README.md CHANGED
@@ -19,7 +19,7 @@ Part of the [RevealUI monorepo](https://github.com/RevealUIStudio/revealui) -
19
19
  - **Plugins** - Extensible plugin system (form builder, nested docs, redirects)
20
20
  - **Feature Gating** - Tier-based licensing (free, pro, max, enterprise) with JWT license keys
21
21
  - **Database** - PostgreSQL adapters (NeonDB + PGlite for testing), connection pooling, SSL/TLS
22
- - **Storage** - Pluggable storage interface (Vercel Blob adapter included)
22
+ - **Storage** - Pluggable storage interface (Cloudflare R2 primary; Vercel Blob legacy adapter retained during migration)
23
23
 
24
24
  ## Installation
25
25
 
@@ -113,27 +113,71 @@ if (isFeatureEnabled('ai')) {
113
113
 
114
114
  | Subpath | Purpose |
115
115
  |---------|---------|
116
- | `@revealui/core` | Main entry (RevealUI instance) |
116
+ | `@revealui/core` | Main entry |
117
117
  | `@revealui/core/server` | Server-side CRUD API |
118
118
  | `@revealui/core/client` | Client-side React hooks and utilities |
119
- | `@revealui/core/config` | Configuration builder |
119
+ | `@revealui/core/client/ui` | Client UI components |
120
+ | `@revealui/core/client/admin` | Client admin components |
121
+ | `@revealui/core/client/richtext` | Client rich text editor |
122
+ | `@revealui/core/config` | Configuration builder (`buildConfig`) |
120
123
  | `@revealui/core/admin` | Admin dashboard components |
124
+ | `@revealui/core/admin/utils/serializeConfig` | Config serialization util |
125
+ | `@revealui/core/admin/utils/apiClient` | Admin API client |
126
+ | `@revealui/core/admin/i18n/en` | English i18n strings |
121
127
  | `@revealui/core/richtext` | Rich text server utilities |
122
128
  | `@revealui/core/richtext/client` | Lexical editor React components |
129
+ | `@revealui/core/richtext/react` | Rich text React exports |
123
130
  | `@revealui/core/richtext/html` | HTML serialization |
124
131
  | `@revealui/core/richtext/rsc` | React Server Component support |
132
+ | `@revealui/core/richtext/lexical` | Raw Lexical exports |
125
133
  | `@revealui/core/security` | Security infrastructure |
126
134
  | `@revealui/core/auth` | Auth utilities |
127
135
  | `@revealui/core/database` | Database adapters |
128
- | `@revealui/core/storage` | Storage adapters |
136
+ | `@revealui/core/database/ssl-config` | SSL/TLS config |
137
+ | `@revealui/core/database/type-adapter` | Type adapter utilities |
138
+ | `@revealui/core/storage` | Storage adapters (R2 + legacy Vercel Blob) |
129
139
  | `@revealui/core/plugins` | Plugin system |
130
140
  | `@revealui/core/features` | Feature flags |
131
141
  | `@revealui/core/license` | License validation |
142
+ | `@revealui/core/license-encryption` | License encryption |
143
+ | `@revealui/core/revforge-license` | RevForge license validation |
132
144
  | `@revealui/core/monitoring` | Health monitoring |
145
+ | `@revealui/core/monitoring/process-registry` | Process registry |
146
+ | `@revealui/core/monitoring/query-monitor` | Query monitor |
147
+ | `@revealui/core/observability` | Observability root |
133
148
  | `@revealui/core/observability/logger` | Structured logging |
149
+ | `@revealui/core/observability/metrics` | Metrics |
150
+ | `@revealui/core/observability/alerts` | Alerts |
151
+ | `@revealui/core/error-handling` | Error boundaries, retry, circuit breaker, fallback UI |
152
+ | `@revealui/core/error-handling/error-reporter` | Error reporting |
153
+ | `@revealui/core/caching` | Caching utilities |
154
+ | `@revealui/core/cache/query-cache` | Query cache |
155
+ | `@revealui/core/jobs` | Background jobs |
156
+ | `@revealui/core/translations` | i18n translations |
157
+ | `@revealui/core/ui` | UI component exports |
134
158
  | `@revealui/core/types` | TypeScript type definitions |
159
+ | `@revealui/core/types/core` | Core types |
160
+ | `@revealui/core/types/schema` | Schema types |
161
+ | `@revealui/core/types/generated` | Generated types |
162
+ | `@revealui/core/types/admin` | Admin types |
163
+ | `@revealui/core/types/frontend` | Frontend types |
164
+ | `@revealui/core/generated` | Generated code |
135
165
  | `@revealui/core/nextjs` | Next.js integration middleware |
136
- | `@revealui/core/utils/*` | Utilities (cache, deep-clone, errors) |
166
+ | `@revealui/core/nextjs/withRevealUI` | Next.js config wrapper |
167
+ | `@revealui/core/api/rest` | REST API handler |
168
+ | `@revealui/core/api/compression` | Response compression |
169
+ | `@revealui/core/api/payload-optimization` | Payload optimization |
170
+ | `@revealui/core/api/rate-limit` | API rate limiting |
171
+ | `@revealui/core/api/response-cache` | Response cache |
172
+ | `@revealui/core/optimization/code-splitting` | Code splitting helpers |
173
+ | `@revealui/core/utils/logger` | Logger utility |
174
+ | `@revealui/core/utils/logger/client` | Client-side logger |
175
+ | `@revealui/core/utils/logger/server` | Server-side logger |
176
+ | `@revealui/core/utils/cache` | Cache utility |
177
+ | `@revealui/core/utils/deep-clone` | Deep clone utility |
178
+ | `@revealui/core/utils/errors` | Error utilities |
179
+ | `@revealui/core/utils/error-responses` | Error response helpers |
180
+ | `@revealui/core/utils/request-context` | Request context |
137
181
 
138
182
  ## Development
139
183
 
@@ -1 +1 @@
1
- {"version":3,"file":"sqlAdapter.d.ts","sourceRoot":"","sources":["../../../src/collections/operations/sqlAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACvE,CAAC;AA6BF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM/C;AAkBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAMvD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpF;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAKjF;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAI1E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAKrF"}
1
+ {"version":3,"file":"sqlAdapter.d.ts","sourceRoot":"","sources":["../../../src/collections/operations/sqlAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACvE,CAAC;AA6BF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM/C;AAkBD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAMvD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpF;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAKjF;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ9D;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAI1E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAKrF"}
@@ -83,7 +83,13 @@ export function insertDocumentQuery(configSlug, columns) {
83
83
  return `INSERT INTO ${collectionTable(configSlug)} (id, ${escapedColumns}) VALUES ($1, ${placeholders})`;
84
84
  }
85
85
  export function selectJsonByIdQuery(configSlug) {
86
- return `SELECT _json FROM ${collectionTable(configSlug)} WHERE id = $1 LIMIT 1`;
86
+ // `id` MUST be in the projection. Query results pass through
87
+ // safeParseRevealDocuments, which drops any row lacking an `id`. A bare
88
+ // `SELECT _json` row has no `id`, so it gets filtered to empty — making
89
+ // update() wrongly throw "Document not found" for every JSON-field
90
+ // collection. Keep `_json` first so existing `sql.includes('SELECT _json')`
91
+ // matchers still recognize this query.
92
+ return `SELECT _json, id FROM ${collectionTable(configSlug)} WHERE id = $1 LIMIT 1`;
87
93
  }
88
94
  export function checkExistsByIdQuery(configSlug) {
89
95
  return `SELECT id FROM ${collectionTable(configSlug)} WHERE id = $1 LIMIT 1`;
package/dist/index.d.ts CHANGED
@@ -27,7 +27,7 @@ export { nestedDocsPlugin } from './plugins/nested-docs.js';
27
27
  export { redirectsPlugin } from './plugins/redirects.js';
28
28
  export { afterChangeTraverseFields, afterReadTraverseFields, beforeChangeTraverseFields, beforeValidateTraverseFields, buildWhereClause, checkDependencies, createRevealUIInstance, defaultLogger, extractWhereValues, flattenResult, getRelationshipFields, Logger, RevealUICollection, RevealUIGlobal, type RevealUILogger, type RichTextAdapter, relationshipPopulationPromise, validateRelationshipMetadata, withNullableJSONSchemaType, } from './revealui.js';
29
29
  export { BoldFeature, FixedToolbarFeature, HeadingFeature, ItalicFeature, LinkFeature, lexicalEditor, TreeViewFeature, UnderlineFeature, } from './richtext/index.js';
30
- export { vercelBlobStorage } from './storage/vercel-blob.js';
30
+ export { objectStorage } from './storage/object-storage.js';
31
31
  export { LRUCache, type LRUCacheOptions } from './utils/cache.js';
32
32
  export { deepClone } from './utils/deep-clone.js';
33
33
  export { createApplicationErrorResponseData, createErrorResponseData, createSuccessResponseData, createValidationErrorResponseData, type ErrorResponseData, } from './utils/error-responses.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -66,7 +66,7 @@ RevealUICollection, RevealUIGlobal, relationshipPopulationPromise, validateRelat
66
66
  // Rich text editor
67
67
  export { BoldFeature, FixedToolbarFeature, HeadingFeature, ItalicFeature, LinkFeature, lexicalEditor, TreeViewFeature, UnderlineFeature, } from './richtext/index.js';
68
68
  // Storage adapters
69
- export { vercelBlobStorage } from './storage/vercel-blob.js';
69
+ export { objectStorage } from './storage/object-storage.js';
70
70
  // Note: Logger class is exported from ./revealui.ts (instance/logger.js), not from utils/logger.js
71
71
  export { LRUCache } from './utils/cache.js';
72
72
  // Deep clone utility
package/dist/license.d.ts CHANGED
@@ -139,6 +139,26 @@ export declare function getCurrentTier(): LicenseTier;
139
139
  * Returns the full license payload, or null if no valid license or cache expired.
140
140
  */
141
141
  export declare function getLicensePayload(): LicensePayload | null;
142
+ /**
143
+ * Returns true when `host` is covered by the license's `domains` claim.
144
+ *
145
+ * The single matching primitive for RevForge/Fleet domain-lock — consumed by
146
+ * the API's `requireDomain` middleware, the admin boot check, and
147
+ * `validateLicenseAtStartup`. Because the allowed domains come from the signed
148
+ * JWT `domains` claim (not a separate env var), the lock is cryptographically
149
+ * bound: it cannot be spoofed by editing an env file.
150
+ *
151
+ * Matching rules (no authored regex, per the fleet no-regex rule):
152
+ * - `host` is lower-cased and stripped of any `:port` suffix
153
+ * - `localhost` / `127.0.0.1` are always allowed, so a trial kit boots and
154
+ * serves on its default `http://localhost` regardless of the licensed domain
155
+ * - otherwise `host` must equal a licensed domain OR be a subdomain of one
156
+ * (`app.example.com` matches `example.com`)
157
+ *
158
+ * @param host raw Host header value or URL hostname (a `:port` suffix is tolerated)
159
+ * @param domains the license payload's `domains` claim
160
+ */
161
+ export declare function hostMatchesLicensedDomains(host: string, domains: readonly string[]): boolean;
142
162
  /**
143
163
  * Checks whether the current license is at least the given tier.
144
164
  * Also validates that the license has not expired (checks JWT exp claim).
@@ -1 +1 @@
1
- {"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"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"}
package/dist/license.js CHANGED
@@ -281,6 +281,36 @@ export function getLicensePayload() {
281
281
  evictStaleCache();
282
282
  return cachedState.payload;
283
283
  }
284
+ /**
285
+ * Returns true when `host` is covered by the license's `domains` claim.
286
+ *
287
+ * The single matching primitive for RevForge/Fleet domain-lock — consumed by
288
+ * the API's `requireDomain` middleware, the admin boot check, and
289
+ * `validateLicenseAtStartup`. Because the allowed domains come from the signed
290
+ * JWT `domains` claim (not a separate env var), the lock is cryptographically
291
+ * bound: it cannot be spoofed by editing an env file.
292
+ *
293
+ * Matching rules (no authored regex, per the fleet no-regex rule):
294
+ * - `host` is lower-cased and stripped of any `:port` suffix
295
+ * - `localhost` / `127.0.0.1` are always allowed, so a trial kit boots and
296
+ * serves on its default `http://localhost` regardless of the licensed domain
297
+ * - otherwise `host` must equal a licensed domain OR be a subdomain of one
298
+ * (`app.example.com` matches `example.com`)
299
+ *
300
+ * @param host raw Host header value or URL hostname (a `:port` suffix is tolerated)
301
+ * @param domains the license payload's `domains` claim
302
+ */
303
+ export function hostMatchesLicensedDomains(host, domains) {
304
+ const normalized = (host.trim().toLowerCase().split(':')[0] ?? '').trim();
305
+ if (!normalized)
306
+ return false;
307
+ if (normalized === 'localhost' || normalized === '127.0.0.1')
308
+ return true;
309
+ return domains.some((domain) => {
310
+ const d = domain.trim().toLowerCase();
311
+ return d.length > 0 && (normalized === d || normalized.endsWith(`.${d}`));
312
+ });
313
+ }
284
314
  /**
285
315
  * Checks whether the current license is at least the given tier.
286
316
  * Also validates that the license has not expired (checks JWT exp claim).
@@ -1 +1 @@
1
- {"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../../src/observability/health-check.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,SAAS,CAAsB;IAEvC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAIlC;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI9B;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC;IAoD1C;;OAEG;YACW,cAAc;IAS5B;;OAEG;IACH,SAAS,IAAI,MAAM;CAGpB;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,mBAA0B,CAAC;AAEnD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,CAmCnF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,gBAAgB,GAAE,MAAW,GAAG,WAAW,CAwClF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,gBAAgB,GAAE,MAAW,GAAG,WAAW,CA4ChF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,GAAE,MAAY,GAAG,WAAW,CAuC3F;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,EACzC,QAAQ,GAAE,OAAe,GACxB,WAAW,CAMb;AAED;;GAEG;AACH,wBAAgB,oBAAoB,UACjB,OAAO,CAAC,QAAQ,CAAC,CAanC;AAED;;GAEG;AACH,wBAAgB,uBAAuB,UACpB,OAAO,CAAC,QAAQ,CAAC,CA2BnC;AAED;;GAEG;AACH,wBAAgB,sBAAsB,UACzB,QAAQ,CAqBpB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,UAAU,GAAE,MAAc,EAC1B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAC9C,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAsBzB"}
1
+ {"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../../src/observability/health-check.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,SAAS,CAAsB;IAEvC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAIlC;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI9B;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC;IAoD1C;;OAEG;YACW,cAAc;IAS5B;;OAEG;IACH,SAAS,IAAI,MAAM;CAGpB;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,mBAA0B,CAAC;AAEnD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,WAAW,CAmCnF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,gBAAgB,GAAE,MAAW,GAAG,WAAW,CA8ClF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,gBAAgB,GAAE,MAAW,GAAG,WAAW,CA4ChF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,GAAE,MAAY,GAAG,WAAW,CAuC3F;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,EACzC,QAAQ,GAAE,OAAe,GACxB,WAAW,CAMb;AAED;;GAEG;AACH,wBAAgB,oBAAoB,UACjB,OAAO,CAAC,QAAQ,CAAC,CAanC;AAED;;GAEG;AACH,wBAAgB,uBAAuB,UACpB,OAAO,CAAC,QAAQ,CAAC,CA2BnC;AAED;;GAEG;AACH,wBAAgB,sBAAsB,UACzB,QAAQ,CAqBpB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,UAAU,GAAE,MAAc,EAC1B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAC9C,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAsBzB"}
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { execFile } from 'node:child_process';
7
7
  import { promisify } from 'node:util';
8
+ import { getHeapStatistics } from 'node:v8';
8
9
  import { logger } from './logger.js';
9
10
  const execFileAsync = promisify(execFile);
10
11
  export class HealthCheckSystem {
@@ -145,7 +146,12 @@ export function createMemoryHealthCheck(thresholdPercent = 90) {
145
146
  };
146
147
  }
147
148
  const usage = process.memoryUsage();
148
- const usedPercent = (usage.heapUsed / usage.heapTotal) * 100;
149
+ // heapTotal is V8's currently-committed heap, not the ceiling — it sits
150
+ // just above heapUsed and grows on demand toward heap_size_limit
151
+ // (--max-old-space-size), so heapUsed/heapTotal reads ~90%+ during normal
152
+ // operation. heap_size_limit is the real OOM-proximity denominator.
153
+ const heapLimit = getHeapStatistics().heap_size_limit;
154
+ const usedPercent = (usage.heapUsed / heapLimit) * 100;
149
155
  let status = 'healthy';
150
156
  let message = 'Memory usage normal';
151
157
  if (usedPercent > thresholdPercent) {
@@ -162,6 +168,7 @@ export function createMemoryHealthCheck(thresholdPercent = 90) {
162
168
  details: {
163
169
  heapUsed: usage.heapUsed,
164
170
  heapTotal: usage.heapTotal,
171
+ heapLimit,
165
172
  usedPercent: usedPercent.toFixed(2),
166
173
  external: usage.external,
167
174
  rss: usage.rss,
@@ -13,7 +13,7 @@
13
13
  */
14
14
  export { createRESTHandlers, handleRESTRequest } from '../api/rest.js';
15
15
  export { universalPostgresAdapter } from '../database/universal-postgres.js';
16
- export { vercelBlobStorage } from '../storage/vercel-blob.js';
16
+ export { objectStorage } from '../storage/object-storage.js';
17
17
  export type { LogContext, Logger, LogLevel } from '../utils/logger-server.js';
18
18
  export { createLogger, logger } from '../utils/logger-server.js';
19
19
  export * from '../utils/request-context.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAEjE,cAAc,6BAA6B,CAAC;AAQ5C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAEjE,cAAc,6BAA6B,CAAC;AAQ5C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
@@ -15,7 +15,7 @@
15
15
  export { createRESTHandlers, handleRESTRequest } from '../api/rest.js';
16
16
  // Server-only database/storage
17
17
  export { universalPostgresAdapter } from '../database/universal-postgres.js';
18
- export { vercelBlobStorage } from '../storage/vercel-blob.js';
18
+ export { objectStorage } from '../storage/object-storage.js';
19
19
  // Server-only logger with request context
20
20
  export { createLogger, logger } from '../utils/logger-server.js';
21
21
  // Request context utilities (server-only - uses async_hooks)
@@ -1,18 +1,19 @@
1
1
  import type { StorageConfig, StorageProvider } from './types.js';
2
2
  export { createMockProvider } from './mock.js';
3
+ export type { ObjectStorageConfig } from './object-storage.js';
4
+ export { objectStorage } from './object-storage.js';
3
5
  export { createR2Provider } from './r2.js';
4
6
  export type { ListItem, ListOptions, ListResult, PutOptions, PutResult, R2Config, StorageConfig, StorageProvider, VercelBlobConfig, } from './types.js';
5
- export { vercelBlobStorage } from './vercel-blob.js';
7
+ export { createVercelBlobProvider } from './vercel-blob-provider.js';
6
8
  /**
7
9
  * Construct a StorageProvider from a tagged config.
8
10
  *
9
11
  * Per `feedback_pluggable_provider_pattern`: explicit "no-X" opt-in — unknown
10
- * or unsupported tags throw with a clear message; no silent fallback.
12
+ * tags throw with a clear message; no silent fallback.
11
13
  *
12
- * Supported tags in Phase 2a: 'r2', 'mock'. The 'vercel-blob' tag is reserved
13
- * for Phase 2b (StorageProvider impl over @vercel/blob); during the migration
14
- * window, consumers needing Vercel Blob should use the legacy `vercelBlobStorage`
15
- * Payload plugin via `revealui.config.ts`.
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.
16
17
  */
17
18
  export declare function createStorage(config: StorageConfig): StorageProvider;
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAK/C,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;AAIpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CAqBpE"}
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"}
@@ -2,43 +2,45 @@
2
2
  // GAP-208 — introduced 2026-05-18 alongside Vercel Blob → Cloudflare R2 swap.
3
3
  //
4
4
  // Phase 1 (#959): types.ts + index.ts type re-exports.
5
- // Phase 2a (this PR): r2 + mock providers + createStorage factory.
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
8
  import { createMockProvider } from './mock.js';
7
9
  import { createR2Provider } from './r2.js';
10
+ import { createVercelBlobProvider } from './vercel-blob-provider.js';
8
11
  export { createMockProvider } from './mock.js';
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.
16
+ export { objectStorage } from './object-storage.js';
9
17
  // Provider factories — exported so callers can construct a provider directly
10
18
  // when they already know which one they want (skipping the discriminated-union
11
19
  // factory below).
12
20
  export { createR2Provider } from './r2.js';
13
- // Legacy Payload-style plugin kept during GAP-208 migration so existing
14
- // `apps/admin/revealui.config.ts` keeps working. Dropped in Phase 4 cleanup.
15
- export { vercelBlobStorage } from './vercel-blob.js';
21
+ export { createVercelBlobProvider } from './vercel-blob-provider.js';
16
22
  /**
17
23
  * Construct a StorageProvider from a tagged config.
18
24
  *
19
25
  * Per `feedback_pluggable_provider_pattern`: explicit "no-X" opt-in — unknown
20
- * or unsupported tags throw with a clear message; no silent fallback.
26
+ * tags throw with a clear message; no silent fallback.
21
27
  *
22
- * Supported tags in Phase 2a: 'r2', 'mock'. The 'vercel-blob' tag is reserved
23
- * for Phase 2b (StorageProvider impl over @vercel/blob); during the migration
24
- * window, consumers needing Vercel Blob should use the legacy `vercelBlobStorage`
25
- * Payload plugin via `revealui.config.ts`.
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.
26
31
  */
27
32
  export function createStorage(config) {
28
33
  switch (config.provider) {
29
34
  case 'r2':
30
35
  return createR2Provider(config.r2);
36
+ case 'vercel-blob':
37
+ return createVercelBlobProvider(config.vercelBlob);
31
38
  case 'mock':
32
39
  return createMockProvider();
33
- case 'vercel-blob':
34
- throw new Error("createStorage: provider 'vercel-blob' is not yet implemented as a " +
35
- 'StorageProvider (lands in GAP-208 Phase 2b). For the migration ' +
36
- 'window, use the legacy `vercelBlobStorage` Payload plugin in ' +
37
- "revealui.config.ts, or switch to provider: 'r2'.");
38
40
  default: {
39
41
  const _exhaustive = config;
40
42
  throw new Error(`createStorage: unknown provider tag. Got ${JSON.stringify(_exhaustive)}. ` +
41
- "Valid tags: 'r2', 'mock', 'vercel-blob' (Phase 2b).");
43
+ "Valid tags: 'r2', 'vercel-blob', 'mock'.");
42
44
  }
43
45
  }
44
46
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Object-storage upload plugin (provider-agnostic).
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.
9
+ *
10
+ * The backend is resolved through a lazy `resolveProvider` thunk — invoked
11
+ * (and memoized) on the first upload/delete, never at config-build time — so a
12
+ * consumer reading validated config (e.g. `@revealui/config`'s `config.storage`)
13
+ * does NOT force env validation when the admin config module is imported during
14
+ * `next build`, tests, or boot. This mirrors `apps/server` `getMediaStorage()`,
15
+ * which reads `config.storage` lazily per request.
16
+ *
17
+ * WARNING: This module is server-only.
18
+ * Do NOT import in client-side code or edge runtime.
19
+ */
20
+ import type { Plugin } from '../types/index.js';
21
+ import type { StorageProvider } from './types.js';
22
+ export interface ObjectStorageConfig {
23
+ /** When `false`, the plugin is a no-op (no upload config applied). Defaults to enabled. */
24
+ enabled?: boolean;
25
+ /** Map of collection slug → whether to attach upload handling. */
26
+ collections?: {
27
+ [key: string]: boolean;
28
+ };
29
+ /**
30
+ * Lazily resolves the backend the upload adapter writes through. Called once
31
+ * (memoized) on the first upload/delete — never at config build — so reading
32
+ * validated config (e.g. `@revealui/config`'s `config.storage`) does not force
33
+ * env validation at module load. Should throw a clear, actionable error when
34
+ * no backend is configured (fail-fast; never silently no-op an upload).
35
+ */
36
+ resolveProvider: () => StorageProvider;
37
+ /** Key prefix for stored objects. Defaults to `'uploads'`. */
38
+ prefix?: string;
39
+ }
40
+ export declare function objectStorage(config: ObjectStorageConfig): Plugin;
41
+ //# sourceMappingURL=object-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object-storage.d.ts","sourceRoot":"","sources":["../../src/storage/object-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,2FAA2F;IAC3F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,WAAW,CAAC,EAAE;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,eAAe,CAAC;IACvC,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAiGjE"}
@@ -1,13 +1,35 @@
1
1
  /**
2
- * Vercel Blob Storage Plugin
2
+ * Object-storage upload plugin (provider-agnostic).
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.
9
+ *
10
+ * The backend is resolved through a lazy `resolveProvider` thunk — invoked
11
+ * (and memoized) on the first upload/delete, never at config-build time — so a
12
+ * consumer reading validated config (e.g. `@revealui/config`'s `config.storage`)
13
+ * does NOT force env validation when the admin config module is imported during
14
+ * `next build`, tests, or boot. This mirrors `apps/server` `getMediaStorage()`,
15
+ * which reads `config.storage` lazily per request.
3
16
  *
4
17
  * WARNING: This module is server-only.
5
18
  * Do NOT import in client-side code or edge runtime.
6
19
  */
7
- import { del, put } from '@vercel/blob';
8
20
  import { defaultLogger } from '../instance/logger.js';
9
- export function vercelBlobStorage(config) {
21
+ export function objectStorage(config) {
10
22
  const prefix = config.prefix || 'uploads';
23
+ // Memoize the provider so the underlying client (S3 / Blob) is constructed
24
+ // once per process, not per upload — and so config is read lazily, on first
25
+ // use, rather than at config-build time.
26
+ let cachedProvider;
27
+ const getProvider = () => {
28
+ if (!cachedProvider) {
29
+ cachedProvider = config.resolveProvider();
30
+ }
31
+ return cachedProvider;
32
+ };
11
33
  return (incomingConfig) => {
12
34
  if (config.enabled === false) {
13
35
  return incomingConfig;
@@ -26,25 +48,21 @@ export function vercelBlobStorage(config) {
26
48
  crop: true,
27
49
  adapters: [
28
50
  {
29
- name: 'vercel-blob',
51
+ name: 'object-storage',
30
52
  adapter: {
31
- name: 'vercel-blob',
53
+ name: 'object-storage',
32
54
  generateURL: (file) => {
33
55
  return `${prefix}/${file.filename}`;
34
56
  },
35
57
  upload: async (file) => {
36
58
  try {
37
- const filePath = `${prefix}/${collection.slug}/${Date.now()}-${file.name}`;
38
- if (!config.token) {
39
- throw new Error('Vercel blob token is required but not configured');
40
- }
41
- const blob = await put(filePath, file.data, {
59
+ const key = `${prefix}/${collection.slug}/${Date.now()}-${file.name}`;
60
+ const result = await getProvider().put(key, file.data, {
42
61
  access: 'public',
43
- token: config.token,
44
- addRandomSuffix: false,
62
+ contentType: file.mimetype,
45
63
  });
46
64
  return {
47
- url: blob.url,
65
+ url: result.url,
48
66
  filename: file.name,
49
67
  filesize: file.size,
50
68
  mimeType: file.mimetype,
@@ -53,20 +71,22 @@ export function vercelBlobStorage(config) {
53
71
  };
54
72
  }
55
73
  catch (error) {
56
- defaultLogger.error('Vercel Blob upload error:', error);
74
+ defaultLogger.error('Object storage upload error:', error);
57
75
  throw error;
58
76
  }
59
77
  },
60
78
  delete: async (filename) => {
61
79
  try {
62
- // Extract the blob URL from filename or construct it
63
- const blobUrl = filename.startsWith('http')
80
+ // Payload may pass a full URL (preferred providers extract
81
+ // the storage key from it) or a bare filename; mirror the
82
+ // legacy plugin's prefix-prepend for the bare-filename case.
83
+ const keyOrUrl = filename.startsWith('http')
64
84
  ? filename
65
85
  : `${prefix}/${filename}`;
66
- await del(blobUrl);
86
+ await getProvider().del(keyOrUrl);
67
87
  }
68
88
  catch (error) {
69
- defaultLogger.error('Vercel Blob delete error:', error);
89
+ defaultLogger.error('Object storage delete error:', error);
70
90
  throw error;
71
91
  }
72
92
  },
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Vercel Blob implementation of StorageProvider.
3
+ *
4
+ * Legacy backend retained during the GAP-208 Cloudflare R2 cutover so the
5
+ * media-upload path can fall back to Vercel Blob when R2 is not configured.
6
+ * Wraps @vercel/blob's put/del/list behind the provider-agnostic
7
+ * StorageProvider interface. Cloudflare R2 (r2.ts) is the canonical backend;
8
+ * this provider is removed once the Blob store is decommissioned.
9
+ *
10
+ * NOTE: distinct from vercel-blob.ts in this directory, which is the legacy
11
+ * Payload-style upload *plugin* still wired into apps/admin's
12
+ * revealui.config.ts. This file is the StorageProvider the createStorage
13
+ * factory returns for the 'vercel-blob' tag.
14
+ *
15
+ * Server-only. Do NOT import from client-side code or edge runtime.
16
+ */
17
+ import type { ListOptions, ListResult, PutOptions, PutResult, StorageProvider, VercelBlobConfig } from './types.js';
18
+ declare class VercelBlobProvider implements StorageProvider {
19
+ readonly provider = "vercel-blob";
20
+ private readonly token;
21
+ constructor(config: VercelBlobConfig);
22
+ put(key: string, data: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>, opts?: PutOptions): Promise<PutResult>;
23
+ del(keyOrUrl: string): Promise<void>;
24
+ list(opts?: ListOptions): Promise<ListResult>;
25
+ }
26
+ export declare function createVercelBlobProvider(config: VercelBlobConfig): StorageProvider;
27
+ export type { VercelBlobProvider };
28
+ //# sourceMappingURL=vercel-blob-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-blob-provider.d.ts","sourceRoot":"","sources":["../../src/storage/vercel-blob-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAEV,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACT,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAKpB,cAAM,kBAAmB,YAAW,eAAe;IACjD,QAAQ,CAAC,QAAQ,iBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,MAAM,EAAE,gBAAgB;IAW9B,GAAG,CACP,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;IAmCf,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpC,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;CAqBpD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,eAAe,CAElF;AAED,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Vercel Blob implementation of StorageProvider.
3
+ *
4
+ * Legacy backend retained during the GAP-208 Cloudflare R2 cutover so the
5
+ * media-upload path can fall back to Vercel Blob when R2 is not configured.
6
+ * Wraps @vercel/blob's put/del/list behind the provider-agnostic
7
+ * StorageProvider interface. Cloudflare R2 (r2.ts) is the canonical backend;
8
+ * this provider is removed once the Blob store is decommissioned.
9
+ *
10
+ * NOTE: distinct from vercel-blob.ts in this directory, which is the legacy
11
+ * Payload-style upload *plugin* still wired into apps/admin's
12
+ * revealui.config.ts. This file is the StorageProvider the createStorage
13
+ * factory returns for the 'vercel-blob' tag.
14
+ *
15
+ * Server-only. Do NOT import from client-side code or edge runtime.
16
+ */
17
+ import { del, list, put } from '@vercel/blob';
18
+ import { toUint8Array } from './_helpers.js';
19
+ const PROVIDER_TAG = 'vercel-blob';
20
+ const DEFAULT_LIST_LIMIT = 1000;
21
+ class VercelBlobProvider {
22
+ provider = PROVIDER_TAG;
23
+ token;
24
+ constructor(config) {
25
+ if (!config.token) {
26
+ throw new Error('Vercel Blob provider requires VercelBlobConfig.token (the ' +
27
+ 'BLOB_READ_WRITE_TOKEN value). For new deployments prefer Cloudflare ' +
28
+ 'R2 (provider: "r2").');
29
+ }
30
+ this.token = config.token;
31
+ }
32
+ async put(key, data, opts) {
33
+ if (opts?.access === 'private') {
34
+ throw new Error("Vercel Blob provider does not support access: 'private' — every blob " +
35
+ 'is publicly readable. Use Cloudflare R2 for private-access patterns.');
36
+ }
37
+ // Normalize to bytes so the result carries an accurate size regardless of
38
+ // input shape (ReadableStream has no length until consumed). Re-wrap in a
39
+ // fresh Uint8Array so the Blob part is backed by a non-shared ArrayBuffer,
40
+ // satisfying the DOM BlobPart type under TS strict typed-array generics.
41
+ const body = await toUint8Array(data);
42
+ const result = await put(key, new Blob([new Uint8Array(body)]), {
43
+ access: 'public',
44
+ token: this.token,
45
+ contentType: opts?.contentType,
46
+ // Keys are caller-controlled (callers pre-randomize, e.g. media/<uuid>.ext);
47
+ // never append a second random suffix or the returned URL won't match `key`.
48
+ addRandomSuffix: false,
49
+ });
50
+ // Vercel Blob has no per-object user-metadata or Cache-Control-header API
51
+ // (only cacheControlMaxAge); opts.metadata / opts.cacheControl are ignored
52
+ // by this legacy provider. R2 honors both.
53
+ return {
54
+ key,
55
+ url: result.url,
56
+ size: body.byteLength,
57
+ provider: PROVIDER_TAG,
58
+ };
59
+ }
60
+ async del(keyOrUrl) {
61
+ // Vercel's del is idempotent (deleting an absent blob is a no-op), so this
62
+ // satisfies the best-effort contract. It requires the full blob URL, which
63
+ // is what media records persist.
64
+ await del(keyOrUrl, { token: this.token });
65
+ }
66
+ async list(opts) {
67
+ const response = await list({
68
+ token: this.token,
69
+ prefix: opts?.prefix,
70
+ limit: opts?.limit ?? DEFAULT_LIST_LIMIT,
71
+ cursor: opts?.cursor,
72
+ });
73
+ const items = response.blobs.map((blob) => ({
74
+ key: blob.pathname,
75
+ url: blob.url,
76
+ size: blob.size,
77
+ uploadedAt: blob.uploadedAt,
78
+ }));
79
+ return {
80
+ items,
81
+ cursor: response.cursor,
82
+ hasMore: response.hasMore,
83
+ };
84
+ }
85
+ }
86
+ export function createVercelBlobProvider(config) {
87
+ return new VercelBlobProvider(config);
88
+ }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@revealui/core",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "RevealUI's runtime engine — admin UI, REST API, auth, rich text, plugin system, and access control. The heart of the open-source platform.",
5
5
  "license": "MIT",
6
6
  "dependencies": {
7
- "@aws-sdk/client-s3": "^3.1049.0",
7
+ "@aws-sdk/client-s3": "^3.1053.0",
8
8
  "@electric-sql/pglite": "^0.4.5",
9
9
  "@lexical/clipboard": "^0.44.0",
10
10
  "@lexical/code": "^0.44.0",
@@ -34,7 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@types/json-schema": "^7.0.15",
36
36
  "@types/pg": "^8.20.0",
37
- "@types/react": "^19.2.14",
37
+ "@types/react": "^19.2.15",
38
38
  "@types/react-dom": "^19.2.3",
39
39
  "@vitest/coverage-v8": "^4.1.5",
40
40
  "expect-type": "^1.3.0",
@@ -1,17 +0,0 @@
1
- /**
2
- * Vercel Blob Storage Plugin
3
- *
4
- * WARNING: This module is server-only.
5
- * Do NOT import in client-side code or edge runtime.
6
- */
7
- import type { Plugin } from '../types/index.js';
8
- export interface VercelBlobStorageConfig {
9
- enabled?: boolean;
10
- collections?: {
11
- [key: string]: boolean;
12
- };
13
- token?: string;
14
- prefix?: string;
15
- }
16
- export declare function vercelBlobStorage(config: VercelBlobStorageConfig): Plugin;
17
- //# sourceMappingURL=vercel-blob.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"vercel-blob.d.ts","sourceRoot":"","sources":["../../src/storage/vercel-blob.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,MAAM,CAyFzE"}