@chainfuse/helpers 3.2.12 → 3.3.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.
- package/dist/common.d.mts +7 -0
- package/dist/common.mjs +9 -0
- package/dist/db.d.mts +81 -0
- package/dist/db.mjs +174 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/net.d.mts +56 -23
- package/dist/net.mjs +77 -13
- package/package.json +10 -5
package/dist/common.d.mts
CHANGED
|
@@ -32,5 +32,12 @@ export declare class Helpers {
|
|
|
32
32
|
* @returns `true` if the running local, otherwise `false`.
|
|
33
33
|
*/
|
|
34
34
|
static isLocal<P extends QwikCityPlatform>(metadataOrPlatform: WorkerVersionMetadata | P): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Pauses execution for a specified number of milliseconds.
|
|
37
|
+
*
|
|
38
|
+
* @param ms - The number of milliseconds to sleep.
|
|
39
|
+
* @returns A promise that resolves after the specified delay.
|
|
40
|
+
*/
|
|
41
|
+
static sleep(ms: number): Promise<void>;
|
|
35
42
|
}
|
|
36
43
|
export {};
|
package/dist/common.mjs
CHANGED
|
@@ -88,4 +88,13 @@ export class Helpers {
|
|
|
88
88
|
return !('request' in metadataOrPlatform);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Pauses execution for a specified number of milliseconds.
|
|
93
|
+
*
|
|
94
|
+
* @param ms - The number of milliseconds to sleep.
|
|
95
|
+
* @returns A promise that resolves after the specified delay.
|
|
96
|
+
*/
|
|
97
|
+
static sleep(ms) {
|
|
98
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
99
|
+
}
|
|
91
100
|
}
|
package/dist/db.d.mts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Cache as DrizzleCache, type MutationOption } from 'drizzle-orm/cache/core';
|
|
2
|
+
import type { CacheConfig } from 'drizzle-orm/cache/core/types';
|
|
3
|
+
import { z } from 'zod/v4';
|
|
4
|
+
/**
|
|
5
|
+
* Interface for CacheStorage-like objects that can be used as drop-in replacements.
|
|
6
|
+
* This interface ensures compatibility with the Web API CacheStorage while allowing for custom implementations that provide the same core functionality.
|
|
7
|
+
*/
|
|
8
|
+
export interface CacheStorageLike {
|
|
9
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open) */
|
|
10
|
+
open(cacheName: string): Promise<Cache>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* SQLCache is a cache implementation for SQL query results, using Web CacheStorage (supports drop in replacements).
|
|
14
|
+
* It supports caching strategies for explicit or global query caching, and provides mechanisms for cache invalidation based on affected tables or tags.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* - Designed for use with Drizzle ORM and compatible with CacheStorage APIs.
|
|
18
|
+
* - Tracks which cache keys are associated with which tables for efficient invalidation.
|
|
19
|
+
* - Supports TTL (time-to-live) configuration and custom caching strategies.
|
|
20
|
+
*
|
|
21
|
+
* @extends DrizzleCache
|
|
22
|
+
*/
|
|
23
|
+
export declare class SQLCache<C extends CacheStorageLike> extends DrizzleCache {
|
|
24
|
+
private dbName;
|
|
25
|
+
private dbType;
|
|
26
|
+
private cache;
|
|
27
|
+
private globalTtl;
|
|
28
|
+
private _strategy;
|
|
29
|
+
private usedTablesPerKey;
|
|
30
|
+
private static constructorArgs;
|
|
31
|
+
/**
|
|
32
|
+
* Creates an instance of the class with the specified database name, type, and cache TTL.
|
|
33
|
+
*
|
|
34
|
+
* @param dbName - The name of the database to use. Must be globally unique as it is used for cache lookup. Will be url encoded if not already url safe.
|
|
35
|
+
* @param dbType - The type of the database (e.g., `d1`, `pg` `mysql`). Will be url encoded if not already url safe.
|
|
36
|
+
* @param cacheTTL - The time-to-live (TTL) value for the cache, in seconds.
|
|
37
|
+
* @param strategy - The caching strategy to use. Defaults to 'explicit'.
|
|
38
|
+
* - `explicit`: The cache is used only when .$withCache() is added to a query.
|
|
39
|
+
* - `all`: All queries are cached globally.
|
|
40
|
+
* @param cacheStore - The cache store to use. Can be a CacheStorage or CacheStorage-like object that atleast contains the `open()` function
|
|
41
|
+
*/
|
|
42
|
+
constructor(args: z.input<ReturnType<(typeof SQLCache)['constructorArgs']>>, cacheStore?: C);
|
|
43
|
+
/**
|
|
44
|
+
* For the strategy, we have two options:
|
|
45
|
+
* - `explicit`: The cache is used only when .$withCache() is added to a query.
|
|
46
|
+
* - `all`: All queries are cached globally.
|
|
47
|
+
* @default 'explicit'
|
|
48
|
+
*/
|
|
49
|
+
strategy(): "explicit" | "all";
|
|
50
|
+
/**
|
|
51
|
+
* Generates a cache key as a `Request` object based on the provided tag or key.
|
|
52
|
+
*
|
|
53
|
+
* @param tagOrKey - An object containing either a `tag` or a `key` property to identify the cache entry.
|
|
54
|
+
* @param init - Optional request initialization parameters, as accepted by the `Request` constructor.
|
|
55
|
+
* @returns A `Request` object representing the cache key, constructed with a URL based on the tag or key and the database configuration.
|
|
56
|
+
*/
|
|
57
|
+
private getCacheKey;
|
|
58
|
+
/**
|
|
59
|
+
* This function accepts query and parameters that cached into key param, allowing you to retrieve response values for this query from the cache.
|
|
60
|
+
* @param key - A hashed query and parameters.
|
|
61
|
+
*/
|
|
62
|
+
get(key: string, _tables: string[], isTag: boolean): Promise<any[] | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* This function accepts several options to define how cached data will be stored:
|
|
65
|
+
* @param hashedQuery - A hashed query and parameters.
|
|
66
|
+
* @param response - An array of values returned by Drizzle from the database.
|
|
67
|
+
* @param tables - An array of tables involved in the select queries. This information is needed for cache invalidation.
|
|
68
|
+
*
|
|
69
|
+
* For example, if a query uses the "users" and "posts" tables, you can store this information. Later, when the app executes any mutation statements on these tables, you can remove the corresponding key from the cache.
|
|
70
|
+
* If you're okay with eventual consistency for your queries, you can skip this option.
|
|
71
|
+
*/
|
|
72
|
+
put(hashedQuery: string, response: any, tables: string[], isTag: boolean, config?: CacheConfig): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* This function is called when insert, update, or delete statements are executed.
|
|
75
|
+
* You can either skip this step or invalidate queries that used the affected tables.
|
|
76
|
+
*
|
|
77
|
+
* @param tags - Used for queries labeled with a specific tag, allowing you to invalidate by that tag.
|
|
78
|
+
* @param tables - The actual tables affected by the insert, update, or delete statements, helping you track which tables have changed since the last cache update.
|
|
79
|
+
*/
|
|
80
|
+
onMutate(params: MutationOption): Promise<void>;
|
|
81
|
+
}
|
package/dist/db.mjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Cache as DrizzleCache } from 'drizzle-orm/cache/core';
|
|
2
|
+
import { is } from 'drizzle-orm/entity';
|
|
3
|
+
import { getTableName, Table } from 'drizzle-orm/table';
|
|
4
|
+
import { z } from 'zod/v4';
|
|
5
|
+
import { CryptoHelpers } from "./crypto.mjs";
|
|
6
|
+
/**
|
|
7
|
+
* SQLCache is a cache implementation for SQL query results, using Web CacheStorage (supports drop in replacements).
|
|
8
|
+
* It supports caching strategies for explicit or global query caching, and provides mechanisms for cache invalidation based on affected tables or tags.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* - Designed for use with Drizzle ORM and compatible with CacheStorage APIs.
|
|
12
|
+
* - Tracks which cache keys are associated with which tables for efficient invalidation.
|
|
13
|
+
* - Supports TTL (time-to-live) configuration and custom caching strategies.
|
|
14
|
+
*
|
|
15
|
+
* @extends DrizzleCache
|
|
16
|
+
*/
|
|
17
|
+
export class SQLCache extends DrizzleCache {
|
|
18
|
+
dbName;
|
|
19
|
+
dbType;
|
|
20
|
+
cache;
|
|
21
|
+
globalTtl;
|
|
22
|
+
_strategy;
|
|
23
|
+
// This object will be used to store which query keys were used
|
|
24
|
+
// for a specific table, so we can later use it for invalidation.
|
|
25
|
+
usedTablesPerKey = {};
|
|
26
|
+
static constructorArgs() {
|
|
27
|
+
return z.object({
|
|
28
|
+
dbName: z
|
|
29
|
+
.string()
|
|
30
|
+
.nonempty()
|
|
31
|
+
.transform((val) => encodeURIComponent(val)),
|
|
32
|
+
dbType: z
|
|
33
|
+
.string()
|
|
34
|
+
.nonempty()
|
|
35
|
+
.transform((val) => encodeURIComponent(val)),
|
|
36
|
+
cacheTTL: z
|
|
37
|
+
.int()
|
|
38
|
+
.nonnegative()
|
|
39
|
+
.default(5 * 60),
|
|
40
|
+
strategy: z.enum(['explicit', 'all']).default('explicit'),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates an instance of the class with the specified database name, type, and cache TTL.
|
|
45
|
+
*
|
|
46
|
+
* @param dbName - The name of the database to use. Must be globally unique as it is used for cache lookup. Will be url encoded if not already url safe.
|
|
47
|
+
* @param dbType - The type of the database (e.g., `d1`, `pg` `mysql`). Will be url encoded if not already url safe.
|
|
48
|
+
* @param cacheTTL - The time-to-live (TTL) value for the cache, in seconds.
|
|
49
|
+
* @param strategy - The caching strategy to use. Defaults to 'explicit'.
|
|
50
|
+
* - `explicit`: The cache is used only when .$withCache() is added to a query.
|
|
51
|
+
* - `all`: All queries are cached globally.
|
|
52
|
+
* @param cacheStore - The cache store to use. Can be a CacheStorage or CacheStorage-like object that atleast contains the `open()` function
|
|
53
|
+
*/
|
|
54
|
+
constructor(args, cacheStore) {
|
|
55
|
+
super();
|
|
56
|
+
const { dbName, dbType, cacheTTL, strategy } = SQLCache.constructorArgs().parse(args);
|
|
57
|
+
this.dbName = dbName;
|
|
58
|
+
this.dbType = dbType;
|
|
59
|
+
cacheStore ??= globalThis.caches;
|
|
60
|
+
if ('open' in cacheStore && typeof cacheStore.open === 'function') {
|
|
61
|
+
this.cache = cacheStore.open(`${dbType}:${dbName}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw new Error('Cache store must be a CacheStorage (or subclass/instance of)');
|
|
65
|
+
}
|
|
66
|
+
this.globalTtl = cacheTTL;
|
|
67
|
+
this._strategy = strategy;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* For the strategy, we have two options:
|
|
71
|
+
* - `explicit`: The cache is used only when .$withCache() is added to a query.
|
|
72
|
+
* - `all`: All queries are cached globally.
|
|
73
|
+
* @default 'explicit'
|
|
74
|
+
*/
|
|
75
|
+
strategy() {
|
|
76
|
+
return this._strategy;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generates a cache key as a `Request` object based on the provided tag or key.
|
|
80
|
+
*
|
|
81
|
+
* @param tagOrKey - An object containing either a `tag` or a `key` property to identify the cache entry.
|
|
82
|
+
* @param init - Optional request initialization parameters, as accepted by the `Request` constructor.
|
|
83
|
+
* @returns A `Request` object representing the cache key, constructed with a URL based on the tag or key and the database configuration.
|
|
84
|
+
*/
|
|
85
|
+
getCacheKey(tagOrKey, init) {
|
|
86
|
+
return new Request(new URL(('tag' in tagOrKey ? ['tag', tagOrKey.tag] : ['key', tagOrKey.key]).join('/'), `https://${this.dbName}.${this.dbType}`), init);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* This function accepts query and parameters that cached into key param, allowing you to retrieve response values for this query from the cache.
|
|
90
|
+
* @param key - A hashed query and parameters.
|
|
91
|
+
*/
|
|
92
|
+
async get(key, _tables, isTag) {
|
|
93
|
+
const response = await this.cache.then(async (cache) => cache.match(this.getCacheKey(isTag ? { tag: key } : { key })));
|
|
94
|
+
console.debug('SQLCache.get', isTag ? 'tag' : 'key', key, response?.ok ? 'HIT' : 'MISS');
|
|
95
|
+
if (response) {
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
97
|
+
return response.json();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return response;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* This function accepts several options to define how cached data will be stored:
|
|
105
|
+
* @param hashedQuery - A hashed query and parameters.
|
|
106
|
+
* @param response - An array of values returned by Drizzle from the database.
|
|
107
|
+
* @param tables - An array of tables involved in the select queries. This information is needed for cache invalidation.
|
|
108
|
+
*
|
|
109
|
+
* For example, if a query uses the "users" and "posts" tables, you can store this information. Later, when the app executes any mutation statements on these tables, you can remove the corresponding key from the cache.
|
|
110
|
+
* If you're okay with eventual consistency for your queries, you can skip this option.
|
|
111
|
+
*/
|
|
112
|
+
async put(hashedQuery, response, tables, isTag, config) {
|
|
113
|
+
let ttl = this.globalTtl;
|
|
114
|
+
if (config?.ex) {
|
|
115
|
+
ttl = config.ex;
|
|
116
|
+
}
|
|
117
|
+
else if (config?.px) {
|
|
118
|
+
ttl = Math.floor(config.px / 1000);
|
|
119
|
+
}
|
|
120
|
+
else if (config?.exat) {
|
|
121
|
+
ttl = Math.floor((new Date(config.exat * 1000).getTime() - Date.now()) / 1000);
|
|
122
|
+
}
|
|
123
|
+
else if (config?.pxat) {
|
|
124
|
+
ttl = Math.floor((new Date(config.pxat).getTime() - Date.now()) / 1000);
|
|
125
|
+
}
|
|
126
|
+
const cacheRequest = new Response(JSON.stringify(response), {
|
|
127
|
+
headers: {
|
|
128
|
+
'Content-Type': 'application/json',
|
|
129
|
+
'Cache-Control': `public, max-age=${ttl}, s-maxage=${ttl}`,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
cacheRequest.headers.set('ETag', await CryptoHelpers.generateETag(cacheRequest));
|
|
133
|
+
await this.cache.then(async (cache) => cache.put(this.getCacheKey(isTag ? { tag: hashedQuery } : { key: hashedQuery }), cacheRequest)).then(() => console.debug('SQLCache.put', isTag ? 'tag' : 'key', hashedQuery, 'SUCCESS'));
|
|
134
|
+
for (const table of tables) {
|
|
135
|
+
const keys = this.usedTablesPerKey[table];
|
|
136
|
+
if (keys === undefined) {
|
|
137
|
+
this.usedTablesPerKey[table] = [hashedQuery];
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
keys.push(hashedQuery);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* This function is called when insert, update, or delete statements are executed.
|
|
146
|
+
* You can either skip this step or invalidate queries that used the affected tables.
|
|
147
|
+
*
|
|
148
|
+
* @param tags - Used for queries labeled with a specific tag, allowing you to invalidate by that tag.
|
|
149
|
+
* @param tables - The actual tables affected by the insert, update, or delete statements, helping you track which tables have changed since the last cache update.
|
|
150
|
+
*/
|
|
151
|
+
async onMutate(params) {
|
|
152
|
+
const tagsArray = params.tags ? (Array.isArray(params.tags) ? params.tags : [params.tags]) : [];
|
|
153
|
+
const tablesArray = params.tables ? (Array.isArray(params.tables) ? params.tables : [params.tables]) : [];
|
|
154
|
+
const keysToDelete = new Set();
|
|
155
|
+
for (const table of tablesArray) {
|
|
156
|
+
const tableName = is(table, Table) ? getTableName(table) : table;
|
|
157
|
+
const keys = this.usedTablesPerKey[tableName] ?? [];
|
|
158
|
+
for (const key of keys)
|
|
159
|
+
keysToDelete.add(key);
|
|
160
|
+
}
|
|
161
|
+
if (keysToDelete.size > 0 || tagsArray.length > 0) {
|
|
162
|
+
for (const tag of tagsArray) {
|
|
163
|
+
await this.cache.then(async (cache) => cache.delete(this.getCacheKey({ tag }))).then(() => console.debug('SQLCache.delete', 'tag', tag, 'SUCCESS'));
|
|
164
|
+
}
|
|
165
|
+
for (const key of keysToDelete) {
|
|
166
|
+
await this.cache.then(async (cache) => cache.delete(this.getCacheKey({ key }))).then(() => console.debug('SQLCache.delete', 'key', key, 'SUCCESS'));
|
|
167
|
+
for (const table of tablesArray) {
|
|
168
|
+
const tableName = is(table, Table) ? getTableName(table) : table;
|
|
169
|
+
this.usedTablesPerKey[tableName] = [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
package/dist/net.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { z } from 'zod/v3';
|
|
2
|
-
|
|
1
|
+
import type { z as z3 } from 'zod/v3';
|
|
2
|
+
import type { z as z4 } from 'zod/v4';
|
|
3
|
+
export type LoggingFetchInitType<RI extends RequestInit = RequestInit> = RI & z3.input<Awaited<ReturnType<typeof NetHelpers.loggingFetchInit>>>;
|
|
3
4
|
/**
|
|
4
5
|
* Enum representing HTTP request methods.
|
|
5
6
|
*
|
|
@@ -17,12 +18,12 @@ export declare enum Methods {
|
|
|
17
18
|
'PATCH' = "PATCH"
|
|
18
19
|
}
|
|
19
20
|
export declare class NetHelpers {
|
|
20
|
-
static cfApiLogging(): Promise<
|
|
21
|
-
level:
|
|
22
|
-
error:
|
|
23
|
-
color:
|
|
24
|
-
custom:
|
|
25
|
-
}, "strip",
|
|
21
|
+
static cfApiLogging(): Promise<z3.ZodDefault<z3.ZodObject<{
|
|
22
|
+
level: z3.ZodDefault<z3.ZodNumber>;
|
|
23
|
+
error: z3.ZodDefault<z3.ZodNumber>;
|
|
24
|
+
color: z3.ZodDefault<z3.ZodBoolean>;
|
|
25
|
+
custom: z3.ZodOptional<z3.ZodFunction<z3.ZodTuple<[], z3.ZodUnknown>, z3.ZodUnion<[z3.ZodVoid, z3.ZodPromise<z3.ZodVoid>]>>>;
|
|
26
|
+
}, "strip", z3.ZodTypeAny, {
|
|
26
27
|
error: number;
|
|
27
28
|
level: number;
|
|
28
29
|
color: boolean;
|
|
@@ -51,14 +52,14 @@ export declare class NetHelpers {
|
|
|
51
52
|
* - Formatting and coloring log output for better readability.
|
|
52
53
|
* - Stripping redundant parts of URLs and wrapping unique IDs in brackets with color coding.
|
|
53
54
|
*/
|
|
54
|
-
static cfApi(apiKey: string, logging?:
|
|
55
|
-
static loggingFetchInit(): Promise<
|
|
56
|
-
logging:
|
|
57
|
-
level:
|
|
58
|
-
error:
|
|
59
|
-
color:
|
|
60
|
-
custom:
|
|
61
|
-
}, "strip",
|
|
55
|
+
static cfApi(apiKey: string, logging?: z3.input<Awaited<ReturnType<typeof NetHelpers.cfApiLogging>>>): Promise<import("cloudflare").Cloudflare>;
|
|
56
|
+
static loggingFetchInit(): Promise<z3.ZodObject<{
|
|
57
|
+
logging: z3.ZodDefault<z3.ZodObject<{
|
|
58
|
+
level: z3.ZodDefault<z3.ZodNumber>;
|
|
59
|
+
error: z3.ZodDefault<z3.ZodNumber>;
|
|
60
|
+
color: z3.ZodDefault<z3.ZodBoolean>;
|
|
61
|
+
custom: z3.ZodOptional<z3.ZodFunction<z3.ZodTuple<[], z3.ZodUnknown>, z3.ZodUnion<[z3.ZodVoid, z3.ZodPromise<z3.ZodVoid>]>>>;
|
|
62
|
+
}, "strip", z3.ZodTypeAny, {
|
|
62
63
|
error: number;
|
|
63
64
|
level: number;
|
|
64
65
|
color: boolean;
|
|
@@ -69,7 +70,7 @@ export declare class NetHelpers {
|
|
|
69
70
|
level?: number | undefined;
|
|
70
71
|
color?: boolean | undefined;
|
|
71
72
|
}>>;
|
|
72
|
-
}, "strip",
|
|
73
|
+
}, "strip", z3.ZodTypeAny, {
|
|
73
74
|
logging: {
|
|
74
75
|
error: number;
|
|
75
76
|
level: number;
|
|
@@ -84,12 +85,12 @@ export declare class NetHelpers {
|
|
|
84
85
|
color?: boolean | undefined;
|
|
85
86
|
} | undefined;
|
|
86
87
|
}>>;
|
|
87
|
-
static loggingFetchInitLogging(): Promise<
|
|
88
|
-
level:
|
|
89
|
-
error:
|
|
90
|
-
color:
|
|
91
|
-
custom:
|
|
92
|
-
}, "strip",
|
|
88
|
+
static loggingFetchInitLogging(): Promise<z3.ZodDefault<z3.ZodObject<{
|
|
89
|
+
level: z3.ZodDefault<z3.ZodNumber>;
|
|
90
|
+
error: z3.ZodDefault<z3.ZodNumber>;
|
|
91
|
+
color: z3.ZodDefault<z3.ZodBoolean>;
|
|
92
|
+
custom: z3.ZodOptional<z3.ZodFunction<z3.ZodTuple<[], z3.ZodUnknown>, z3.ZodUnion<[z3.ZodVoid, z3.ZodPromise<z3.ZodVoid>]>>>;
|
|
93
|
+
}, "strip", z3.ZodTypeAny, {
|
|
93
94
|
error: number;
|
|
94
95
|
level: number;
|
|
95
96
|
color: boolean;
|
|
@@ -171,4 +172,36 @@ export declare class NetHelpers {
|
|
|
171
172
|
* @returns {Record<string, number | null>} An object where keys are metric names (with optional descriptions) and values are the durations in milliseconds or null.
|
|
172
173
|
*/
|
|
173
174
|
static serverTiming(serverTimingHeader?: string): Record<string, number | null>;
|
|
175
|
+
static withRetryInit(): Promise<z4.ZodDefault<z4.ZodObject<{
|
|
176
|
+
maxRetries: z4.ZodDefault<z4.ZodInt>;
|
|
177
|
+
initialDelay: z4.ZodDefault<z4.ZodInt>;
|
|
178
|
+
maxDelay: z4.ZodDefault<z4.ZodInt>;
|
|
179
|
+
backoffFactor: z4.ZodDefault<z4.ZodInt>;
|
|
180
|
+
}, z4.core.$strip>>>;
|
|
181
|
+
/**
|
|
182
|
+
* Executes an asynchronous operation with configurable retry logic.
|
|
183
|
+
*
|
|
184
|
+
* The operation function can be a simple parameterless function or a function that has been bound with arguments (using .bind(), arrow functions, or closures).
|
|
185
|
+
*
|
|
186
|
+
* @param operation - A function that returns a Promise. Arguments should be bound/captured beforehand.
|
|
187
|
+
* @param config - Optional retry configuration
|
|
188
|
+
* @returns Promise that resolves to the result of the operation
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* // Simple function with no arguments
|
|
192
|
+
* await NetHelpers.withRetry(() => fetch('/api/data'))
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* // Function with arguments using arrow function closure
|
|
196
|
+
* await NetHelpers.withRetry(() => fetch(url, options))
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* // Function with arguments using .bind()
|
|
200
|
+
* await NetHelpers.withRetry(fetch.bind(null, url, options))
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* // With custom retry configuration
|
|
204
|
+
* await NetHelpers.withRetry(() => apiCall(), { maxRetries: 5, initialDelay: 200 })
|
|
205
|
+
*/
|
|
206
|
+
static withRetry<T>(operation: () => Promise<T>, config?: z4.input<Awaited<ReturnType<typeof NetHelpers.withRetryInit>>>): Promise<T>;
|
|
174
207
|
}
|
package/dist/net.mjs
CHANGED
|
@@ -17,15 +17,15 @@ export var Methods;
|
|
|
17
17
|
})(Methods || (Methods = {}));
|
|
18
18
|
export class NetHelpers {
|
|
19
19
|
static cfApiLogging() {
|
|
20
|
-
return import('zod').then(({ z }) =>
|
|
20
|
+
return import('zod/v3').then(({ z: z3 }) => z3
|
|
21
21
|
.object({
|
|
22
|
-
level:
|
|
23
|
-
error:
|
|
24
|
-
color:
|
|
25
|
-
custom:
|
|
22
|
+
level: z3.coerce.number().int().min(0).max(3).default(0),
|
|
23
|
+
error: z3.coerce.number().int().min(0).max(3).default(1),
|
|
24
|
+
color: z3.boolean().default(true),
|
|
25
|
+
custom: z3
|
|
26
26
|
.function()
|
|
27
27
|
.args()
|
|
28
|
-
.returns(
|
|
28
|
+
.returns(z3.union([z3.void(), z3.promise(z3.void())]))
|
|
29
29
|
.optional(),
|
|
30
30
|
})
|
|
31
31
|
.default({}));
|
|
@@ -162,19 +162,19 @@ export class NetHelpers {
|
|
|
162
162
|
}));
|
|
163
163
|
}
|
|
164
164
|
static loggingFetchInit() {
|
|
165
|
-
return Promise.all([import('zod'), this.loggingFetchInitLogging()]).then(([{ z }, logging]) =>
|
|
165
|
+
return Promise.all([import('zod/v3'), this.loggingFetchInitLogging()]).then(([{ z: z3 }, logging]) => z3.object({
|
|
166
166
|
logging,
|
|
167
167
|
}));
|
|
168
168
|
}
|
|
169
169
|
static loggingFetchInitLogging() {
|
|
170
|
-
return import('zod').then(({ z }) =>
|
|
170
|
+
return import('zod/v3').then(({ z: z3 }) => z3
|
|
171
171
|
.object({
|
|
172
|
-
level:
|
|
173
|
-
error:
|
|
174
|
-
color:
|
|
175
|
-
custom:
|
|
172
|
+
level: z3.coerce.number().int().min(0).max(3).default(0),
|
|
173
|
+
error: z3.coerce.number().int().min(0).max(3).default(1),
|
|
174
|
+
color: z3.boolean().default(true),
|
|
175
|
+
custom: z3
|
|
176
176
|
.function()
|
|
177
|
-
.returns(
|
|
177
|
+
.returns(z3.union([z3.void(), z3.promise(z3.void())]))
|
|
178
178
|
.optional(),
|
|
179
179
|
})
|
|
180
180
|
.default({}));
|
|
@@ -501,4 +501,68 @@ export class NetHelpers {
|
|
|
501
501
|
}
|
|
502
502
|
return result;
|
|
503
503
|
}
|
|
504
|
+
static withRetryInit() {
|
|
505
|
+
return import('zod/v4').then(({ z: z4 }) => z4
|
|
506
|
+
.object({
|
|
507
|
+
maxRetries: z4.int().nonnegative().default(3),
|
|
508
|
+
initialDelay: z4.int().nonnegative().default(100),
|
|
509
|
+
maxDelay: z4.int().nonnegative().default(1000),
|
|
510
|
+
backoffFactor: z4.int().nonnegative().default(2),
|
|
511
|
+
})
|
|
512
|
+
.default({
|
|
513
|
+
maxRetries: 3,
|
|
514
|
+
initialDelay: 100,
|
|
515
|
+
maxDelay: 1000,
|
|
516
|
+
backoffFactor: 2,
|
|
517
|
+
}));
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Executes an asynchronous operation with configurable retry logic.
|
|
521
|
+
*
|
|
522
|
+
* The operation function can be a simple parameterless function or a function that has been bound with arguments (using .bind(), arrow functions, or closures).
|
|
523
|
+
*
|
|
524
|
+
* @param operation - A function that returns a Promise. Arguments should be bound/captured beforehand.
|
|
525
|
+
* @param config - Optional retry configuration
|
|
526
|
+
* @returns Promise that resolves to the result of the operation
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* // Simple function with no arguments
|
|
530
|
+
* await NetHelpers.withRetry(() => fetch('/api/data'))
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* // Function with arguments using arrow function closure
|
|
534
|
+
* await NetHelpers.withRetry(() => fetch(url, options))
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* // Function with arguments using .bind()
|
|
538
|
+
* await NetHelpers.withRetry(fetch.bind(null, url, options))
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
* // With custom retry configuration
|
|
542
|
+
* await NetHelpers.withRetry(() => apiCall(), { maxRetries: 5, initialDelay: 200 })
|
|
543
|
+
*/
|
|
544
|
+
static withRetry(operation, config) {
|
|
545
|
+
return Promise.all([NetHelpers.withRetryInit().then((parser) => parser.parseAsync(config)), import('./common.mjs')]).then(async ([config, { Helpers }]) => {
|
|
546
|
+
let lastError;
|
|
547
|
+
let delay = config.initialDelay;
|
|
548
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
549
|
+
try {
|
|
550
|
+
const result = await operation();
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
lastError = error;
|
|
555
|
+
if (attempt === config.maxRetries) {
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
// Add randomness to avoid synchronizing retries
|
|
559
|
+
// Wait for a random delay between delay and delay*2
|
|
560
|
+
await Helpers.sleep(delay * (1 + Math.random()));
|
|
561
|
+
// Calculate next delay with exponential backoff
|
|
562
|
+
delay = Math.min(delay * config.backoffFactor, config.maxDelay);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
throw lastError;
|
|
566
|
+
});
|
|
567
|
+
}
|
|
504
568
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chainfuse/helpers",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "ChainFuse",
|
|
6
6
|
"homepage": "https://github.com/ChainFuse/packages/tree/main/packages/helpers#readme",
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
"import": "./dist/crypto.js",
|
|
58
58
|
"types": "./dist/crypto.d.ts"
|
|
59
59
|
},
|
|
60
|
+
"./db": {
|
|
61
|
+
"import": "./dist/db.js",
|
|
62
|
+
"types": "./dist/db.d.ts"
|
|
63
|
+
},
|
|
60
64
|
"./discord": {
|
|
61
65
|
"import": "./dist/discord.js",
|
|
62
66
|
"types": "./dist/discord.d.ts"
|
|
@@ -72,17 +76,18 @@
|
|
|
72
76
|
},
|
|
73
77
|
"prettier": "@demosjarco/prettier-config",
|
|
74
78
|
"dependencies": {
|
|
75
|
-
"@chainfuse/types": "^2.10.
|
|
79
|
+
"@chainfuse/types": "^2.10.21",
|
|
76
80
|
"@discordjs/rest": "^2.5.0",
|
|
77
81
|
"chalk": "^5.4.1",
|
|
78
82
|
"cloudflare": "^4.3.0",
|
|
83
|
+
"drizzle-orm": "^0.44.2",
|
|
79
84
|
"strip-ansi": "^7.1.0",
|
|
80
85
|
"uuid": "^11.1.0",
|
|
81
|
-
"zod": "^3.25.
|
|
86
|
+
"zod": "^3.25.51"
|
|
82
87
|
},
|
|
83
88
|
"devDependencies": {
|
|
84
|
-
"@cloudflare/workers-types": "^4.
|
|
89
|
+
"@cloudflare/workers-types": "^4.20250605.0",
|
|
85
90
|
"@types/node": "^22.15.29"
|
|
86
91
|
},
|
|
87
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "726cf6c05c96cf75a1213019de78835853d470aa"
|
|
88
93
|
}
|