Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions examples/cache_storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const cache = await caches.open("test-cache-simple");
console.log("✓ Opened cache successfully");

const hasCache = await caches.has("test-cache-simple");
console.log("✓ Cache exists:", hasCache);

await cache.add("https://api.example.com/simple");
console.log("✓ Added URL to cache: https://api.example.com/simple");
const matchResult = await cache.match("https://api.example.com/simple");
console.log("✓ Cache match result:", matchResult !== undefined);
console.log(" Match type:", typeof matchResult);
console.log(" Match value:", matchResult);

await cache.add("https://api.example.com/data1");
await cache.add("https://api.example.com/data2");
await cache.add("https://api.example.com/data3");

const match1 = await cache.match("https://api.example.com/data1");
const match2 = await cache.match("https://api.example.com/data2");
const match3 = await cache.match("https://api.example.com/data3");
const nonMatch = await cache.match("https://api.example.com/nonexistent");

console.log("✓ Match data1:", match1 !== undefined);
console.log("✓ Match data2:", match2 !== undefined);
console.log("✓ Match data3:", match3 !== undefined);
console.log("✓ Non-existent match:", nonMatch === undefined);

const keys = await cache.keys();
console.log(
"✓ Cache keys count:",
Array.isArray(keys) ? keys.length : "not an array",
);
console.log(" Keys type:", typeof keys);
const deleted = await cache.delete("https://api.example.com/data1");
console.log("✓ Delete result:", deleted);

const deletedMatch = await cache.match("https://api.example.com/data1");
console.log("✓ Deleted URL no longer matches:", deletedMatch === undefined);
await caches.open("another-cache");
const cacheNames = await caches.keys();
console.log("✓ Cache names:", cacheNames);
const deletedCache = await caches.delete("test-cache-simple");
const deletedAnother = await caches.delete("another-cache");
console.log("✓ Deleted test-cache-simple:", deletedCache);
console.log("✓ Deleted another-cache:", deletedAnother);
1 change: 1 addition & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ tokio.workspace = true
oxc-miette.workspace = true
oxc_diagnostics.workspace = true
serde.workspace = true
serde_json.workspace = true
url.workspace = true
base64-simd.workspace = true
image = { workspace = true, optional = true }
Expand Down
161 changes: 161 additions & 0 deletions runtime/src/ext/cache_storage/cache_storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// deno-lint-ignore-file no-explicit-any
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

/**
* Implementation of the Cache and CacheStorage APIs for Andromeda
* Based on: https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage
* Spec: https://w3c.github.io/ServiceWorker/#cache-interface
*
* Note: This is a simplified implementation that wraps the native sync functions
* in promises to maintain compatibility with the Web API specification.
*/

type RequestInfo = Request | string | URL;

interface CacheQueryOptions {
ignoreSearch?: boolean;
ignoreMethod?: boolean;
ignoreVary?: boolean;
}

class Cache {
#cacheName: string;

constructor(cacheName: string) {
this.#cacheName = cacheName;
}

/**
* Returns a Promise that resolves to the response associated with the first matching request in the Cache object.
*/
match(
request: RequestInfo,
options?: CacheQueryOptions,
): Promise<Response | undefined> {
return Promise.resolve(
cache_match(this.#cacheName, request as any, options),
);
}

/**
* Returns a Promise that resolves to an array of all matching responses in the Cache object.
*/
matchAll(
request?: RequestInfo,
options?: CacheQueryOptions,
): Promise<Response[]> {
return Promise.resolve(
cache_matchAll(this.#cacheName, request as any, options),
);
}

/**
* Takes a URL, retrieves it and adds the resulting response object to the given cache.
*/
add(request: RequestInfo): Promise<void> {
return Promise.resolve(cache_add(this.#cacheName, request as any));
}

/**
* Takes an array of URLs, retrieves them, and adds the resulting response objects to the given cache.
*/
addAll(requests: RequestInfo[]): Promise<void> {
return Promise.resolve(cache_addAll(this.#cacheName, requests as any));
}

/**
* Takes both a request and its response and adds it to the given cache.
*/
put(request: RequestInfo, response: Response): Promise<void> {
// Clone the response to ensure it can be consumed
const responseClone = response.clone();
return Promise.resolve(
cache_put(this.#cacheName, request as any, responseClone),
);
}

/**
* Finds the Cache entry whose key is the request, and if found, deletes the Cache entry and returns a Promise that resolves to true.
*/
delete(
request: RequestInfo,
options?: CacheQueryOptions,
): Promise<boolean> {
return Promise.resolve(
cache_delete(this.#cacheName, request as any, options),
);
}

/**
* Returns a Promise that resolves to an array of Cache keys.
*/
keys(
request?: RequestInfo,
options?: CacheQueryOptions,
): Promise<Request[]> {
return Promise.resolve(
cache_keys(this.#cacheName, request as any, options),
);
}
}

class CacheStorage {
/**
* Returns a Promise that resolves to the Cache object matching the cacheName.
*/
open(cacheName: string): Promise<Cache> {
// Call the sync function
cacheStorage_open(cacheName);
return Promise.resolve(new Cache(cacheName));
}

/**
* Returns a Promise that resolves to true if a Cache object matching the cacheName exists.
*/
has(cacheName: string): Promise<boolean> {
return Promise.resolve(cacheStorage_has(cacheName));
}

/**
* Finds the Cache object matching the cacheName, and if found, deletes the Cache object and returns a Promise that resolves to true.
*/
delete(cacheName: string): Promise<boolean> {
return Promise.resolve(cacheStorage_delete(cacheName));
}

/**
* Returns a Promise that will resolve with an array containing strings corresponding to all of the named Cache objects.
*/
keys(): Promise<string[]> {
return Promise.resolve(cacheStorage_keys());
}

/**
* Checks if a given Request is a key in any of the Cache objects that the CacheStorage object tracks.
*/
match(
request: RequestInfo,
options?: CacheQueryOptions,
): Promise<Response | undefined> {
return Promise.resolve(cacheStorage_match(request as any, options));
}
}

// Create global CacheStorage instance
let cacheStorageInstance: CacheStorage | undefined;

function getCacheStorage(): CacheStorage {
if (!cacheStorageInstance) {
cacheStorageInstance = new CacheStorage();
}
return cacheStorageInstance;
}

// Define the global 'caches' property
Object.defineProperty(globalThis, "caches", {
get: () => getCacheStorage(),
configurable: true,
enumerable: true,
});
Loading
Loading