unconventional 1.13.2 → 1.14.1
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.
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
import { CloudflareKVAPI } from "../utils/cloudflare.utils";
|
|
2
3
|
import { BaseModel } from "./base.model";
|
|
3
4
|
export declare class BaseCache {
|
|
4
5
|
private static kvKeySizeLimit;
|
|
6
|
+
private static kvApi;
|
|
7
|
+
static setKVApi(config: CloudflareKVAPI): Promise<void>;
|
|
5
8
|
static get<T>(cache: KVNamespace, key: string): Promise<T | null>;
|
|
6
9
|
static set<T>(cache: KVNamespace, key: string, value: T, ttl?: number): Promise<void>;
|
|
7
10
|
static delete(cache: KVNamespace, key: string): Promise<void>;
|
package/dist/core/base.cache.js
CHANGED
|
@@ -3,9 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.BaseCache = void 0;
|
|
4
4
|
const api_types_1 = require("../types/api.types");
|
|
5
5
|
const array_utils_1 = require("../utils/array.utils");
|
|
6
|
+
const cloudflare_utils_1 = require("../utils/cloudflare.utils");
|
|
6
7
|
const crypto_utils_1 = require("../utils/crypto.utils");
|
|
7
8
|
class BaseCache {
|
|
8
9
|
static kvKeySizeLimit = 512;
|
|
10
|
+
static kvApi;
|
|
11
|
+
static async setKVApi(config) {
|
|
12
|
+
this.kvApi = new cloudflare_utils_1.CloudflareKV(config);
|
|
13
|
+
}
|
|
9
14
|
static async get(cache, key) {
|
|
10
15
|
if (key.length > this.kvKeySizeLimit)
|
|
11
16
|
return null;
|
|
@@ -44,21 +49,22 @@ class BaseCache {
|
|
|
44
49
|
const identifier = model?.$id();
|
|
45
50
|
if (!identifier)
|
|
46
51
|
return;
|
|
47
|
-
//
|
|
52
|
+
// Get keys by ID
|
|
48
53
|
const idList = await cache.list({ prefix: `${api_types_1.CachePrefix.Record}${model.constructor.name}:${identifier.toString()}` });
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
// Delete by Key
|
|
53
|
-
let keyPromises = [];
|
|
54
|
+
const keysToDelete = idList.keys.map(key => key.name);
|
|
55
|
+
// Get keys by Key
|
|
54
56
|
if (model.$key()) {
|
|
55
57
|
const keyList = await cache.list({ prefix: `${api_types_1.CachePrefix.Record}${model.constructor.name}:${model.$key()}` });
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
keysToDelete.push(...keyList.keys.map(key => key.name));
|
|
59
|
+
}
|
|
60
|
+
if (this.kvApi) {
|
|
61
|
+
// Use bulk delete if KV API is available
|
|
62
|
+
await this.kvApi.deleteKeys(keysToDelete);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Fallback to parallel individual deletes
|
|
66
|
+
await Promise.all(keysToDelete.map(key => this.delete(cache, key)));
|
|
59
67
|
}
|
|
60
|
-
// Execute
|
|
61
|
-
await Promise.all([...idPromises, ...keyPromises]);
|
|
62
68
|
}
|
|
63
69
|
//#endregion
|
|
64
70
|
//#region Page Methods
|
|
@@ -77,9 +83,15 @@ class BaseCache {
|
|
|
77
83
|
/** Deletes a page from the cache. Deletes all modified versions too. */
|
|
78
84
|
static async clearPage(cache, model) {
|
|
79
85
|
const list = await cache.list({ prefix: `${api_types_1.CachePrefix.Page}${model.name}` });
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
const keys = list.keys.map(key => key.name);
|
|
87
|
+
if (this.kvApi) {
|
|
88
|
+
// Use bulk delete if KV API is available
|
|
89
|
+
await this.kvApi.deleteKeys(keys);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Fallback to parallel individual deletes
|
|
93
|
+
await Promise.all(keys.map(key => this.delete(cache, key)));
|
|
94
|
+
}
|
|
83
95
|
}
|
|
84
96
|
//#endregion
|
|
85
97
|
//#region Helpers
|
|
@@ -2,9 +2,20 @@ import { MiddlewareHandler } from "hono";
|
|
|
2
2
|
import { DBFactory } from "./db.types";
|
|
3
3
|
import { cors } from "hono/cors";
|
|
4
4
|
export interface ServerConfig {
|
|
5
|
+
/** Name of the server. */
|
|
5
6
|
name: string;
|
|
7
|
+
/** Base path for all routes. */
|
|
6
8
|
basePath?: string;
|
|
9
|
+
/** Middleware to apply to all routes. */
|
|
7
10
|
middleware?: MiddlewareHandler[];
|
|
11
|
+
/**
|
|
12
|
+
* Options for CORS middleware.
|
|
13
|
+
* If not provided, all origins and methods are permitted by default.
|
|
14
|
+
*/
|
|
8
15
|
cors?: Parameters<typeof cors>[0];
|
|
16
|
+
/**
|
|
17
|
+
* Factory producing a database driver.
|
|
18
|
+
* Required to communicate with a database via BaseModel's methods.
|
|
19
|
+
*/
|
|
9
20
|
getDB: DBFactory;
|
|
10
21
|
}
|
|
@@ -14,7 +14,12 @@ async function getCachedOrFetch(cache, key, fetchFn, ttl = api_types_1.DefaultCa
|
|
|
14
14
|
const response = await fetchFn();
|
|
15
15
|
// Cache if successful
|
|
16
16
|
if (response && cache) {
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
|
+
await cache.put(key, JSON.stringify(response), { expirationTtl: ttl });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
// Suppress cache errors (e.g. rate limiting) so response is still returned
|
|
22
|
+
}
|
|
18
23
|
}
|
|
19
24
|
return response;
|
|
20
25
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
interface CloudflareMessage {
|
|
2
|
+
code: number;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
interface CloudflareResponse {
|
|
6
|
+
success: boolean;
|
|
7
|
+
errors: CloudflareMessage[];
|
|
8
|
+
messages: CloudflareMessage[];
|
|
9
|
+
}
|
|
10
|
+
interface BulkDeleteResponse extends CloudflareResponse {
|
|
11
|
+
result: {
|
|
12
|
+
successful_key_count: number;
|
|
13
|
+
unsuccessful_keys: string[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface CloudflareKVAPI {
|
|
17
|
+
accountId: string;
|
|
18
|
+
apiToken: string;
|
|
19
|
+
namespaceId: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class CloudflareKV {
|
|
22
|
+
private api;
|
|
23
|
+
private url;
|
|
24
|
+
constructor(api: CloudflareKVAPI);
|
|
25
|
+
/**
|
|
26
|
+
* Deletes multiple keys from a KV namespace
|
|
27
|
+
* @param namespaceId The namespace to delete keys from
|
|
28
|
+
* @param keys Array of key names to delete (max 10,000)
|
|
29
|
+
*/
|
|
30
|
+
deleteKeys(keys: string[]): Promise<BulkDeleteResponse>;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CloudflareKV = void 0;
|
|
4
|
+
class CloudflareKV {
|
|
5
|
+
api;
|
|
6
|
+
url;
|
|
7
|
+
constructor(api) {
|
|
8
|
+
this.api = api;
|
|
9
|
+
this.url = `https://api.cloudflare.com/client/v4/accounts/${this.api.accountId}/storage/kv/namespaces/${this.api.namespaceId}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Deletes multiple keys from a KV namespace
|
|
13
|
+
* @param namespaceId The namespace to delete keys from
|
|
14
|
+
* @param keys Array of key names to delete (max 10,000)
|
|
15
|
+
*/
|
|
16
|
+
async deleteKeys(keys) {
|
|
17
|
+
if (keys.length > 10000) {
|
|
18
|
+
throw new Error('Cannot delete more than 10,000 keys at once');
|
|
19
|
+
}
|
|
20
|
+
const response = await fetch(`${this.url}/bulk/delete`, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: {
|
|
23
|
+
'Authorization': `Bearer ${this.api.apiToken}`,
|
|
24
|
+
'Content-Type': 'application/json'
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify(keys)
|
|
27
|
+
});
|
|
28
|
+
return response.json();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.CloudflareKV = CloudflareKV;
|