@revealui/cache 0.2.0 → 0.2.2
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/LICENSE.commercial +63 -85
- package/README.md +109 -0
- package/dist/adapters/index.d.ts +126 -0
- package/dist/adapters/index.js +144 -0
- package/dist/browser-7BTPENLH.js +6 -0
- package/dist/chunk-EPAGOXMX.js +123 -0
- package/dist/index.d.ts +134 -18
- package/dist/index.js +184 -19
- package/dist/types-CmU1eRbl.d.ts +34 -0
- package/package.json +34 -17
- package/dist/index.js.map +0 -1
package/LICENSE.commercial
CHANGED
|
@@ -1,112 +1,90 @@
|
|
|
1
|
-
RevealUI Commercial
|
|
2
|
-
Version 1.0, February 2026
|
|
1
|
+
RevealUI Studio — Commercial Licensing for Pro Packages
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
This file is an explainer for the dual-license structure of this repository.
|
|
4
|
+
It is NOT a standalone license document; the canonical license terms for
|
|
5
|
+
each Pro package live in that package's own LICENSE file.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
## Repository Licensing Overview
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
Most of this repository (the OSS packages, apps, scripts, docs, and tooling)
|
|
10
|
+
is licensed under the MIT License. See LICENSE in the repository root for
|
|
11
|
+
those terms.
|
|
9
12
|
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
A small number of "Pro" packages are licensed under the Functional Source
|
|
14
|
+
License v1.1 with MIT Future License (FSL-1.1-MIT) instead of MIT. Those
|
|
15
|
+
packages each carry their own LICENSE file containing the canonical FSL
|
|
16
|
+
terms; this file documents which packages are covered and what FSL-1.1-MIT
|
|
17
|
+
permits and restricts.
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
paid subscription at https://revealui.com.
|
|
19
|
+
## Pro Packages Covered
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
The following packages distributed in this repository are licensed under
|
|
22
|
+
FSL-1.1-MIT, not MIT:
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
end users, integrating the Software into a product or service, or using the
|
|
25
|
-
Software in a revenue-generating capacity.
|
|
24
|
+
- @revealui/ai (see packages/ai/LICENSE)
|
|
25
|
+
- @revealui/harnesses (see packages/harnesses/LICENSE)
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Additional Pro packages may be added in future. The presence of a LICENSE
|
|
28
|
+
file inside a package directory containing the FSL-1.1-MIT text is the
|
|
29
|
+
authoritative signal that the package is governed by FSL terms rather than
|
|
30
|
+
the root MIT LICENSE.
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
is granted a non-exclusive, non-transferable, revocable license to:
|
|
32
|
+
## What FSL-1.1-MIT Permits
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
(b) Modify the Software for internal use.
|
|
34
|
-
(c) Deploy the Software on infrastructure controlled by the Licensee.
|
|
34
|
+
Source code for Pro packages is publicly available. You may:
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
- Use the package internally for any purpose, including commercial use
|
|
37
|
+
- Modify the package and distribute derivative works under the same terms
|
|
38
|
+
- Inspect, audit, and debug the source
|
|
39
|
+
- Depend on the package from your own software
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
(e) Remove or replace RevealUI branding (white-label).
|
|
40
|
-
(f) Use the Software for multiple tenants within the Licensee's
|
|
41
|
-
organization or customer base.
|
|
41
|
+
## What FSL-1.1-MIT Restricts
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
You may NOT make the functionality of a Pro package available to third
|
|
44
|
+
parties as a service that competes with RevealUI Studio's commercial
|
|
45
|
+
offerings. The full restriction text in each per-package LICENSE controls;
|
|
46
|
+
in plain language, prohibited uses include:
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
- Hosting the package as part of a SaaS that primarily delivers the
|
|
49
|
+
package's functionality
|
|
50
|
+
- Offering a service whose value derives entirely or primarily from the
|
|
51
|
+
package
|
|
52
|
+
- Repackaging the functionality for redistribution as a competing service
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
(b) Redistribute, sublicense, sell, or otherwise transfer the Software
|
|
50
|
-
or any portion of it to third parties.
|
|
51
|
-
(c) Remove, alter, or circumvent the license key verification
|
|
52
|
-
functionality of the Software.
|
|
53
|
-
(d) Use the Software in Production without a valid License Key.
|
|
54
|
-
(e) Share, publish, or make the License Key available to unauthorized
|
|
55
|
-
parties.
|
|
54
|
+
If you are unsure whether your intended use is restricted, contact
|
|
55
|
+
founder@revealui.com before relying on the package.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
## Change Date and MIT Conversion
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
Each Pro package's LICENSE file specifies a Change Date. On the earlier of
|
|
60
|
+
that date or the fourth anniversary of the first publicly-available
|
|
61
|
+
distribution of a specific version of the package under FSL, the package
|
|
62
|
+
automatically converts to the MIT License (the "Change License"). After
|
|
63
|
+
conversion, all FSL restrictions are removed for the version in question
|
|
64
|
+
and going forward.
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
This means every Pro package in this repository will eventually become MIT;
|
|
67
|
+
FSL-1.1-MIT is a time-limited restriction, not a permanent one.
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
Upon cancellation or expiration of the subscription:
|
|
69
|
+
## Commercial Licensing Alternatives
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
of the Software.
|
|
71
|
+
If FSL-1.1-MIT terms do not fit your use case (for example, you are
|
|
72
|
+
building a service that the FSL would prohibit, or you need contractual
|
|
73
|
+
guarantees beyond what an open license provides), commercial licensing is
|
|
74
|
+
available.
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
Contact: founder@revealui.com
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
This License does not grant any ownership rights. Contributions to
|
|
78
|
-
commercial portions of the Software require a Contributor License Agreement.
|
|
78
|
+
## Canonical FSL-1.1-MIT Text
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
The Functional Source License v1.1 with MIT Future License is published at:
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
Files under the MIT License (as indicated in the root LICENSE file) are not
|
|
84
|
-
subject to this commercial license and may be used freely under MIT terms.
|
|
82
|
+
https://fsl.software/FSL-1.1-MIT.template.md
|
|
85
83
|
|
|
86
|
-
|
|
84
|
+
The full canonical text is reproduced verbatim in each Pro package's
|
|
85
|
+
LICENSE file. If there is any conflict between this explainer and a
|
|
86
|
+
per-package LICENSE file, the per-package LICENSE file controls.
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
90
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
88
|
+
---
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
IN NO EVENT SHALL REVEALUI STUDIO BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
95
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
96
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
97
|
-
DEALINGS IN THE SOFTWARE, EXCEEDING THE AMOUNT PAID BY THE LICENSEE IN
|
|
98
|
-
THE TWELVE (12) MONTHS PRECEDING THE CLAIM.
|
|
99
|
-
|
|
100
|
-
10. GOVERNING LAW
|
|
101
|
-
|
|
102
|
-
This License shall be governed by the laws of the State of California,
|
|
103
|
-
United States of America, without regard to its conflict of law provisions.
|
|
104
|
-
|
|
105
|
-
11. ENTIRE AGREEMENT
|
|
106
|
-
|
|
107
|
-
This License constitutes the entire agreement between the parties with
|
|
108
|
-
respect to the commercial portions of the Software and supersedes all
|
|
109
|
-
prior agreements, understandings, and communications.
|
|
110
|
-
|
|
111
|
-
For licensing inquiries: founder@revealui.com
|
|
112
|
-
For pricing and subscriptions: https://revealui.com/pricing
|
|
90
|
+
Copyright (c) 2025-2026 RevealUI Studio
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "@revealui/cache"
|
|
3
|
+
description: "Caching infrastructure for RevealUI applications. Provides CDN cache configuration, edge cache helpers, ISR presets, tag-based revalidation, and rate limiting at the edge."
|
|
4
|
+
visibility: public
|
|
5
|
+
status: verified
|
|
6
|
+
audience: user
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# @revealui/cache
|
|
10
|
+
|
|
11
|
+
Caching infrastructure for RevealUI applications. Provides CDN cache configuration, edge cache helpers, ISR presets, tag-based revalidation, and rate limiting at the edge.
|
|
12
|
+
|
|
13
|
+
## When to Use This
|
|
14
|
+
|
|
15
|
+
- You need Cache-Control headers for CDN responses (Vercel, Cloudflare)
|
|
16
|
+
- You want ISR presets for Next.js pages (static, dynamic, real-time)
|
|
17
|
+
- You need tag-based cache invalidation when content changes
|
|
18
|
+
- You want edge-level rate limiting or A/B test variant assignment
|
|
19
|
+
- You need cache warming for static paths
|
|
20
|
+
|
|
21
|
+
If you're caching in-memory data within a single request, use standard `Map` or LRU - this package is for HTTP-layer and CDN caching.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add @revealui/cache
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
No peer dependencies. ISR helpers use structural typing compatible with `NextRequest`/`NextResponse`, Hono, and Cloudflare Workers - no `next` package required.
|
|
30
|
+
|
|
31
|
+
## API Reference
|
|
32
|
+
|
|
33
|
+
### CDN Configuration
|
|
34
|
+
|
|
35
|
+
| Export | Type | Purpose |
|
|
36
|
+
|--------|------|---------|
|
|
37
|
+
| `generateCacheControl` | Function | Build Cache-Control header string from config |
|
|
38
|
+
| `getCacheTTL` | Function | Get TTL for a content type |
|
|
39
|
+
| `CDN_CACHE_PRESETS` | Object | Pre-built configs (static, api, dynamic, immutable) |
|
|
40
|
+
| `DEFAULT_CDN_CONFIG` | Object | Default CDN configuration |
|
|
41
|
+
| `generateCacheTags` | Function | Generate cache tags for content-based invalidation |
|
|
42
|
+
| `generateVercelCacheConfig` | Function | Vercel-specific cache headers |
|
|
43
|
+
| `generateCloudflareConfig` | Function | Cloudflare-specific cache config |
|
|
44
|
+
| `shouldCacheResponse` | Function | Determine if a response should be cached |
|
|
45
|
+
|
|
46
|
+
### CDN Purge
|
|
47
|
+
|
|
48
|
+
| Export | Type | Purpose |
|
|
49
|
+
|--------|------|---------|
|
|
50
|
+
| `purgeCDNCache` | Function | Purge CDN cache by URL patterns |
|
|
51
|
+
| `purgeCacheByTag` | Function | Purge by cache tag (content-type based) |
|
|
52
|
+
| `purgeAllCache` | Function | Full CDN cache purge |
|
|
53
|
+
| `warmCDNCache` | Function | Pre-warm cache for a list of URLs |
|
|
54
|
+
|
|
55
|
+
### Edge Cache & ISR
|
|
56
|
+
|
|
57
|
+
| Export | Type | Purpose |
|
|
58
|
+
|--------|------|---------|
|
|
59
|
+
| `ISR_PRESETS` | Object | Next.js ISR configs (static: 1h, dynamic: 60s, realtime: 10s, immutable: 1y) |
|
|
60
|
+
| `revalidatePath` | Function | Revalidate a single Next.js path |
|
|
61
|
+
| `revalidatePaths` | Function | Batch path revalidation |
|
|
62
|
+
| `revalidateTag` | Function | Revalidate by cache tag |
|
|
63
|
+
| `revalidateTags` | Function | Batch tag revalidation |
|
|
64
|
+
| `generateStaticParams` | Function | Helper for Next.js static generation |
|
|
65
|
+
| `setEdgeCacheHeaders` | Function | Set edge-specific cache headers on response |
|
|
66
|
+
| `createEdgeCachedFetch` | Function | Fetch wrapper with edge caching |
|
|
67
|
+
| `createCachedFunction` | Function | Memoize an async function with TTL |
|
|
68
|
+
| `warmISRCache` | Function | Pre-warm ISR cache for static paths |
|
|
69
|
+
| `addPreloadLinks` | Function | Add `Link: <url>; rel=preload` headers |
|
|
70
|
+
|
|
71
|
+
### Edge Utilities
|
|
72
|
+
|
|
73
|
+
| Export | Type | Purpose |
|
|
74
|
+
|--------|------|---------|
|
|
75
|
+
| `EdgeRateLimiter` | Class | Token bucket rate limiter for edge functions |
|
|
76
|
+
| `getGeoLocation` | Function | Extract geo data from edge request headers |
|
|
77
|
+
| `getABTestVariant` | Function | Deterministic A/B test variant assignment |
|
|
78
|
+
| `getPersonalizationConfig` | Function | Edge personalization based on geo/device |
|
|
79
|
+
|
|
80
|
+
### Invalidation Channel
|
|
81
|
+
|
|
82
|
+
| Export | Type | Purpose |
|
|
83
|
+
|--------|------|---------|
|
|
84
|
+
| `CacheInvalidationChannel` | Class | Distributed cache busting via pub/sub channel |
|
|
85
|
+
|
|
86
|
+
### Configuration
|
|
87
|
+
|
|
88
|
+
| Export | Type | Purpose |
|
|
89
|
+
|--------|------|---------|
|
|
90
|
+
| `configureCacheLogger` | Function | Set custom logger (defaults to console) |
|
|
91
|
+
|
|
92
|
+
### Subpath Exports
|
|
93
|
+
|
|
94
|
+
| Subpath | Contents |
|
|
95
|
+
|---------|----------|
|
|
96
|
+
| `@revealui/cache` | All cache utilities |
|
|
97
|
+
| `@revealui/cache/adapters` | `CacheStore` interface + implementations (for custom backends) |
|
|
98
|
+
|
|
99
|
+
## Design Principles
|
|
100
|
+
|
|
101
|
+
- **Adaptive**: ISR presets scale from real-time (10s) to immutable (1y) based on content volatility
|
|
102
|
+
- **Unified**: Cache tags follow the same taxonomy as CMS collections - invalidation is automatic
|
|
103
|
+
- **Orthogonal**: Caching is a separate concern from content serving - swap CDN providers without changing business logic
|
|
104
|
+
|
|
105
|
+
## Related Packages
|
|
106
|
+
|
|
107
|
+
- `apps/server` - Applies cache headers to REST responses
|
|
108
|
+
- `apps/marketing` - Uses ISR presets for marketing pages
|
|
109
|
+
- `@revealui/core` - Triggers cache invalidation on content changes
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { C as CacheStore } from '../types-CmU1eRbl.js';
|
|
2
|
+
export { a as CacheEntry } from '../types-CmU1eRbl.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Browser PGlite Cache Store
|
|
6
|
+
*
|
|
7
|
+
* Creates a PGlite WASM instance in the browser backed by IndexedDB,
|
|
8
|
+
* then wraps it with PGliteCacheStore for SQL-powered client-side caching.
|
|
9
|
+
*
|
|
10
|
+
* Benefits over localStorage:
|
|
11
|
+
* - SQL queries for filtering cached data
|
|
12
|
+
* - Tag-based and prefix-based invalidation
|
|
13
|
+
* - IndexedDB storage (much larger than localStorage's ~5MB)
|
|
14
|
+
* - Shared CacheStore interface with server-side cache
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const cache = await createBrowserCache();
|
|
18
|
+
* await cache.set('posts:123', postData, 3600, ['posts']);
|
|
19
|
+
* const data = await cache.get('posts:123');
|
|
20
|
+
* await cache.close(); // on unmount
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface BrowserCacheOptions {
|
|
24
|
+
/** IndexedDB database name for persistence (default: 'revealui-cache') */
|
|
25
|
+
dbName?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a browser-compatible PGlite cache store.
|
|
29
|
+
*
|
|
30
|
+
* Dynamically imports @electric-sql/pglite (WASM) to avoid bundling
|
|
31
|
+
* it in server builds. The PGlite instance uses IndexedDB for persistence
|
|
32
|
+
* so cached data survives page reloads.
|
|
33
|
+
*/
|
|
34
|
+
declare function createBrowserCache(options?: BrowserCacheOptions): Promise<CacheStore>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* In-Memory Cache Store
|
|
38
|
+
*
|
|
39
|
+
* Map-backed cache store. Fast, zero-dependency, single-instance only.
|
|
40
|
+
* Use for development, testing, or when distributed state isn't needed.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
declare class InMemoryCacheStore implements CacheStore {
|
|
44
|
+
private store;
|
|
45
|
+
private maxEntries;
|
|
46
|
+
constructor(options?: {
|
|
47
|
+
maxEntries?: number;
|
|
48
|
+
});
|
|
49
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
50
|
+
set<T = unknown>(key: string, value: T, ttlSeconds: number, tags?: string[]): Promise<void>;
|
|
51
|
+
delete(...keys: string[]): Promise<number>;
|
|
52
|
+
deleteByPrefix(prefix: string): Promise<number>;
|
|
53
|
+
deleteByTags(tags: string[]): Promise<number>;
|
|
54
|
+
clear(): Promise<void>;
|
|
55
|
+
size(): Promise<number>;
|
|
56
|
+
prune(): Promise<number>;
|
|
57
|
+
close(): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* PGlite Cache Store
|
|
62
|
+
*
|
|
63
|
+
* PostgreSQL-compatible cache store backed by PGlite (in-memory or file-based).
|
|
64
|
+
* Provides the same CacheStore interface as InMemoryCacheStore but uses SQL
|
|
65
|
+
* for persistence and querying - enabling distributed invalidation via
|
|
66
|
+
* ElectricSQL shape subscriptions in Phase 5.10C.
|
|
67
|
+
*
|
|
68
|
+
* Table schema is auto-created on first use (no external migrations needed).
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/** Minimal PGlite interface - avoids importing the full @electric-sql/pglite package. */
|
|
72
|
+
interface PGliteInstance {
|
|
73
|
+
exec(query: string): Promise<unknown>;
|
|
74
|
+
query<T = Record<string, unknown>>(query: string, params?: unknown[]): Promise<{
|
|
75
|
+
rows: T[];
|
|
76
|
+
}>;
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
interface PGliteCacheStoreOptions {
|
|
80
|
+
/** PGlite instance (caller owns lifecycle unless closeOnDestroy is true). */
|
|
81
|
+
db: PGliteInstance;
|
|
82
|
+
/** Table name prefix to avoid collisions (default: none). */
|
|
83
|
+
tablePrefix?: string;
|
|
84
|
+
/** Close the PGlite instance when close() is called (default: false). */
|
|
85
|
+
closeOnDestroy?: boolean;
|
|
86
|
+
}
|
|
87
|
+
declare class PGliteCacheStore implements CacheStore {
|
|
88
|
+
private db;
|
|
89
|
+
private ready;
|
|
90
|
+
private closeOnDestroy;
|
|
91
|
+
constructor(options: PGliteCacheStoreOptions);
|
|
92
|
+
private init;
|
|
93
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
94
|
+
set<T = unknown>(key: string, value: T, ttlSeconds: number, tags?: string[]): Promise<void>;
|
|
95
|
+
delete(...keys: string[]): Promise<number>;
|
|
96
|
+
deleteByPrefix(prefix: string): Promise<number>;
|
|
97
|
+
deleteByTags(tags: string[]): Promise<number>;
|
|
98
|
+
clear(): Promise<void>;
|
|
99
|
+
size(): Promise<number>;
|
|
100
|
+
prune(): Promise<number>;
|
|
101
|
+
close(): Promise<void>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface UseBrowserCacheResult {
|
|
105
|
+
/** The PGlite-backed CacheStore instance. Null while initializing. */
|
|
106
|
+
cache: CacheStore | null;
|
|
107
|
+
/** Whether the cache is still being initialized. */
|
|
108
|
+
loading: boolean;
|
|
109
|
+
/** Initialization error, if any. */
|
|
110
|
+
error: Error | null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Access the browser-side PGlite cache store.
|
|
114
|
+
*
|
|
115
|
+
* Returns a shared singleton CacheStore. Multiple components can
|
|
116
|
+
* use this hook without creating duplicate PGlite instances.
|
|
117
|
+
*
|
|
118
|
+
* Example:
|
|
119
|
+
* const { cache, loading } = useBrowserCache();
|
|
120
|
+
* if (!loading && cache) {
|
|
121
|
+
* const data = await cache.get('posts:recent');
|
|
122
|
+
* }
|
|
123
|
+
*/
|
|
124
|
+
declare function useBrowserCache(): UseBrowserCacheResult;
|
|
125
|
+
|
|
126
|
+
export { CacheStore, InMemoryCacheStore, PGliteCacheStore, createBrowserCache, useBrowserCache };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PGliteCacheStore,
|
|
3
|
+
createBrowserCache
|
|
4
|
+
} from "../chunk-EPAGOXMX.js";
|
|
5
|
+
|
|
6
|
+
// src/adapters/memory.ts
|
|
7
|
+
var InMemoryCacheStore = class {
|
|
8
|
+
store = /* @__PURE__ */ new Map();
|
|
9
|
+
maxEntries;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.maxEntries = options?.maxEntries ?? 1e4;
|
|
12
|
+
}
|
|
13
|
+
async get(key) {
|
|
14
|
+
const entry = this.store.get(key);
|
|
15
|
+
if (!entry) return null;
|
|
16
|
+
if (Date.now() > entry.expiresAt) {
|
|
17
|
+
this.store.delete(key);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return JSON.parse(entry.value);
|
|
21
|
+
}
|
|
22
|
+
async set(key, value, ttlSeconds, tags) {
|
|
23
|
+
if (this.store.size >= this.maxEntries && !this.store.has(key)) {
|
|
24
|
+
const firstKey = this.store.keys().next().value;
|
|
25
|
+
if (firstKey !== void 0) {
|
|
26
|
+
this.store.delete(firstKey);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
this.store.set(key, {
|
|
30
|
+
value: JSON.stringify(value),
|
|
31
|
+
expiresAt: Date.now() + ttlSeconds * 1e3,
|
|
32
|
+
tags: tags ?? []
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async delete(...keys) {
|
|
36
|
+
let count = 0;
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
if (this.store.delete(key)) count++;
|
|
39
|
+
}
|
|
40
|
+
return count;
|
|
41
|
+
}
|
|
42
|
+
async deleteByPrefix(prefix) {
|
|
43
|
+
let count = 0;
|
|
44
|
+
for (const key of this.store.keys()) {
|
|
45
|
+
if (key.startsWith(prefix)) {
|
|
46
|
+
this.store.delete(key);
|
|
47
|
+
count++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return count;
|
|
51
|
+
}
|
|
52
|
+
async deleteByTags(tags) {
|
|
53
|
+
const tagSet = new Set(tags);
|
|
54
|
+
let count = 0;
|
|
55
|
+
for (const [key, entry] of this.store.entries()) {
|
|
56
|
+
if (entry.tags.some((t) => tagSet.has(t))) {
|
|
57
|
+
this.store.delete(key);
|
|
58
|
+
count++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return count;
|
|
62
|
+
}
|
|
63
|
+
async clear() {
|
|
64
|
+
this.store.clear();
|
|
65
|
+
}
|
|
66
|
+
async size() {
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
let count = 0;
|
|
69
|
+
for (const entry of this.store.values()) {
|
|
70
|
+
if (entry.expiresAt > now) count++;
|
|
71
|
+
}
|
|
72
|
+
return count;
|
|
73
|
+
}
|
|
74
|
+
async prune() {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
let pruned = 0;
|
|
77
|
+
for (const [key, entry] of this.store.entries()) {
|
|
78
|
+
if (entry.expiresAt <= now) {
|
|
79
|
+
this.store.delete(key);
|
|
80
|
+
pruned++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return pruned;
|
|
84
|
+
}
|
|
85
|
+
async close() {
|
|
86
|
+
this.store.clear();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/adapters/use-browser-cache.ts
|
|
91
|
+
import { useEffect, useRef, useState } from "react";
|
|
92
|
+
var sharedCache = null;
|
|
93
|
+
var initPromise = null;
|
|
94
|
+
var refCount = 0;
|
|
95
|
+
async function getOrCreateCache() {
|
|
96
|
+
if (sharedCache) return sharedCache;
|
|
97
|
+
if (initPromise) return initPromise;
|
|
98
|
+
initPromise = import("../browser-7BTPENLH.js").then(async (mod) => {
|
|
99
|
+
const cache = await mod.createBrowserCache();
|
|
100
|
+
sharedCache = cache;
|
|
101
|
+
return cache;
|
|
102
|
+
});
|
|
103
|
+
return initPromise;
|
|
104
|
+
}
|
|
105
|
+
function useBrowserCache() {
|
|
106
|
+
const [cache, setCache] = useState(sharedCache);
|
|
107
|
+
const [loading, setLoading] = useState(!sharedCache);
|
|
108
|
+
const [error, setError] = useState(null);
|
|
109
|
+
const mounted = useRef(true);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
mounted.current = true;
|
|
112
|
+
refCount++;
|
|
113
|
+
if (!sharedCache) {
|
|
114
|
+
getOrCreateCache().then((c) => {
|
|
115
|
+
if (mounted.current) {
|
|
116
|
+
setCache(c);
|
|
117
|
+
setLoading(false);
|
|
118
|
+
}
|
|
119
|
+
}).catch((err) => {
|
|
120
|
+
if (mounted.current) {
|
|
121
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
122
|
+
setLoading(false);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return () => {
|
|
127
|
+
mounted.current = false;
|
|
128
|
+
refCount--;
|
|
129
|
+
if (refCount === 0 && sharedCache) {
|
|
130
|
+
sharedCache.close().catch(() => {
|
|
131
|
+
});
|
|
132
|
+
sharedCache = null;
|
|
133
|
+
initPromise = null;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}, []);
|
|
137
|
+
return { cache, loading, error };
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
InMemoryCacheStore,
|
|
141
|
+
PGliteCacheStore,
|
|
142
|
+
createBrowserCache,
|
|
143
|
+
useBrowserCache
|
|
144
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/adapters/pglite.ts
|
|
2
|
+
var CREATE_TABLE_SQL = `
|
|
3
|
+
CREATE TABLE IF NOT EXISTS _cache_entries (
|
|
4
|
+
key TEXT PRIMARY KEY,
|
|
5
|
+
value TEXT NOT NULL,
|
|
6
|
+
expires_at BIGINT NOT NULL,
|
|
7
|
+
tags TEXT[] NOT NULL DEFAULT '{}'
|
|
8
|
+
);
|
|
9
|
+
CREATE INDEX IF NOT EXISTS _cache_entries_expires_idx ON _cache_entries (expires_at);
|
|
10
|
+
`;
|
|
11
|
+
var PGliteCacheStore = class {
|
|
12
|
+
db;
|
|
13
|
+
ready;
|
|
14
|
+
closeOnDestroy;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.db = options.db;
|
|
17
|
+
this.closeOnDestroy = options.closeOnDestroy ?? false;
|
|
18
|
+
this.ready = this.init();
|
|
19
|
+
}
|
|
20
|
+
async init() {
|
|
21
|
+
await this.db.exec(CREATE_TABLE_SQL);
|
|
22
|
+
}
|
|
23
|
+
async get(key) {
|
|
24
|
+
await this.ready;
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const result = await this.db.query(
|
|
27
|
+
"SELECT value FROM _cache_entries WHERE key = $1 AND expires_at > $2",
|
|
28
|
+
[key, now]
|
|
29
|
+
);
|
|
30
|
+
const row = result.rows[0];
|
|
31
|
+
if (!row) return null;
|
|
32
|
+
return JSON.parse(row.value);
|
|
33
|
+
}
|
|
34
|
+
async set(key, value, ttlSeconds, tags) {
|
|
35
|
+
await this.ready;
|
|
36
|
+
const expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
37
|
+
const serialized = JSON.stringify(value);
|
|
38
|
+
const tagArray = tags ?? [];
|
|
39
|
+
await this.db.query(
|
|
40
|
+
`INSERT INTO _cache_entries (key, value, expires_at, tags)
|
|
41
|
+
VALUES ($1, $2, $3, $4)
|
|
42
|
+
ON CONFLICT (key) DO UPDATE
|
|
43
|
+
SET value = EXCLUDED.value, expires_at = EXCLUDED.expires_at, tags = EXCLUDED.tags`,
|
|
44
|
+
[key, serialized, expiresAt, tagArray]
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
async delete(...keys) {
|
|
48
|
+
await this.ready;
|
|
49
|
+
if (keys.length === 0) return 0;
|
|
50
|
+
const placeholders = keys.map((_, i) => `$${i + 1}`).join(", ");
|
|
51
|
+
const result = await this.db.query(
|
|
52
|
+
`WITH deleted AS (DELETE FROM _cache_entries WHERE key IN (${placeholders}) RETURNING 1)
|
|
53
|
+
SELECT count(*)::text AS count FROM deleted`,
|
|
54
|
+
keys
|
|
55
|
+
);
|
|
56
|
+
return Number.parseInt(result.rows[0]?.count ?? "0", 10);
|
|
57
|
+
}
|
|
58
|
+
async deleteByPrefix(prefix) {
|
|
59
|
+
await this.ready;
|
|
60
|
+
const escaped = prefix.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
|
|
61
|
+
const result = await this.db.query(
|
|
62
|
+
`WITH deleted AS (DELETE FROM _cache_entries WHERE key LIKE $1 ESCAPE '\\' RETURNING 1)
|
|
63
|
+
SELECT count(*)::text AS count FROM deleted`,
|
|
64
|
+
[`${escaped}%`]
|
|
65
|
+
);
|
|
66
|
+
return Number.parseInt(result.rows[0]?.count ?? "0", 10);
|
|
67
|
+
}
|
|
68
|
+
async deleteByTags(tags) {
|
|
69
|
+
await this.ready;
|
|
70
|
+
if (tags.length === 0) return 0;
|
|
71
|
+
const result = await this.db.query(
|
|
72
|
+
`WITH deleted AS (DELETE FROM _cache_entries WHERE tags && $1 RETURNING 1)
|
|
73
|
+
SELECT count(*)::text AS count FROM deleted`,
|
|
74
|
+
[tags]
|
|
75
|
+
);
|
|
76
|
+
return Number.parseInt(result.rows[0]?.count ?? "0", 10);
|
|
77
|
+
}
|
|
78
|
+
async clear() {
|
|
79
|
+
await this.ready;
|
|
80
|
+
await this.db.exec("DELETE FROM _cache_entries");
|
|
81
|
+
}
|
|
82
|
+
async size() {
|
|
83
|
+
await this.ready;
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
const result = await this.db.query(
|
|
86
|
+
"SELECT count(*)::text AS count FROM _cache_entries WHERE expires_at > $1",
|
|
87
|
+
[now]
|
|
88
|
+
);
|
|
89
|
+
return Number.parseInt(result.rows[0]?.count ?? "0", 10);
|
|
90
|
+
}
|
|
91
|
+
async prune() {
|
|
92
|
+
await this.ready;
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const result = await this.db.query(
|
|
95
|
+
`WITH deleted AS (DELETE FROM _cache_entries WHERE expires_at <= $1 RETURNING 1)
|
|
96
|
+
SELECT count(*)::text AS count FROM deleted`,
|
|
97
|
+
[now]
|
|
98
|
+
);
|
|
99
|
+
return Number.parseInt(result.rows[0]?.count ?? "0", 10);
|
|
100
|
+
}
|
|
101
|
+
async close() {
|
|
102
|
+
if (this.closeOnDestroy) {
|
|
103
|
+
await this.db.close();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/adapters/browser.ts
|
|
109
|
+
async function createBrowserCache(options) {
|
|
110
|
+
const dbName = options?.dbName ?? "revealui-cache";
|
|
111
|
+
const { PGlite } = await import("@electric-sql/pglite");
|
|
112
|
+
const db = new PGlite(`idb://${dbName}`);
|
|
113
|
+
await db.waitReady;
|
|
114
|
+
return new PGliteCacheStore({
|
|
115
|
+
db,
|
|
116
|
+
closeOnDestroy: true
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export {
|
|
121
|
+
PGliteCacheStore,
|
|
122
|
+
createBrowserCache
|
|
123
|
+
};
|