@chainfuse/helpers 0.0.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/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # `@chainfuse/helpers`
2
+
3
+ > TODO: description
4
+
5
+ ## Usage
6
+
7
+ ```
8
+ import helpers from '@chainfuse/helpers';
9
+
10
+ // TODO: DEMONSTRATE API
11
+ ```
@@ -0,0 +1,12 @@
1
+ import type { UndefinedProperties } from '@chainfuse/types';
2
+ import type { PrefixedUuid, UuidExport } from '@chainfuse/types/d1';
3
+ export declare class BufferHelpers {
4
+ static bufferFromHex(hex: UuidExport['hex']): Promise<UuidExport['blob']>;
5
+ static bufferToHex(buffer: UuidExport['blob']): Promise<UuidExport['hex']>;
6
+ static get generateUuid(): Promise<UuidExport>;
7
+ static uuidConvert(input: UuidExport['blob']): Promise<UuidExport>;
8
+ static uuidConvert(prefixedUtf: PrefixedUuid): Promise<UuidExport>;
9
+ static uuidConvert(input: UuidExport['utf8']): Promise<UuidExport>;
10
+ static uuidConvert(input: UuidExport['hex']): Promise<UuidExport>;
11
+ static uuidConvert(input: undefined): Promise<UndefinedProperties<UuidExport>>;
12
+ }
@@ -0,0 +1,76 @@
1
+ import { CryptoHelpers } from './crypto.mjs';
2
+ export class BufferHelpers {
3
+ static bufferFromHex(hex) {
4
+ return (import('node:buffer')
5
+ .then(({ Buffer }) => {
6
+ const mainBuffer = Buffer.from(hex, 'hex');
7
+ return mainBuffer.buffer.slice(mainBuffer.byteOffset, mainBuffer.byteOffset + mainBuffer.byteLength);
8
+ })
9
+ /**
10
+ * @link https://jsbm.dev/NHJHj31Zwm3OP
11
+ */
12
+ .catch(() => new Uint8Array(hex.length / 2).map((_, index) => parseInt(hex.slice(index * 2, index * 2 + 2), 16)).buffer));
13
+ }
14
+ static bufferToHex(buffer) {
15
+ return (import('node:buffer')
16
+ .then(({ Buffer }) => Buffer.from(buffer).toString('hex'))
17
+ /**
18
+ * @link https://jsbm.dev/AoXo8dEke1GUg
19
+ */
20
+ .catch(() => new Uint8Array(buffer).reduce((output, elem) => output + ('0' + elem.toString(16)).slice(-2), '')));
21
+ }
22
+ static get generateUuid() {
23
+ return Promise.all([CryptoHelpers.secretBytes(16), import('uuid')]).then(([random, { v7: uuidv7 }]) => {
24
+ const uuid = uuidv7({ random });
25
+ const uuidHex = uuid.replaceAll('-', '');
26
+ return this.bufferFromHex(uuidHex).then((blob) => ({
27
+ utf8: uuid,
28
+ hex: uuidHex,
29
+ blob,
30
+ }));
31
+ });
32
+ }
33
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
34
+ static uuidConvert(input) {
35
+ if (input) {
36
+ if (typeof input === 'string') {
37
+ if (input.includes('-')) {
38
+ let hex = input.replaceAll('-', '');
39
+ if (input.includes('_')) {
40
+ input = input.split('_')[1];
41
+ hex = hex.split('_')[1];
42
+ }
43
+ return this.bufferFromHex(hex).then((blob) => ({
44
+ utf8: input,
45
+ hex,
46
+ blob,
47
+ }));
48
+ }
49
+ else {
50
+ const hex = input;
51
+ return this.bufferFromHex(hex).then((blob) => ({
52
+ utf8: `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}`,
53
+ hex,
54
+ blob,
55
+ }));
56
+ }
57
+ }
58
+ else {
59
+ const blob = input;
60
+ return this.bufferToHex(blob).then((hex) => ({
61
+ utf8: `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20)}`,
62
+ hex,
63
+ blob,
64
+ }));
65
+ }
66
+ }
67
+ else {
68
+ // eslint-disable-next-line @typescript-eslint/require-await
69
+ return (async () => ({
70
+ utf8: undefined,
71
+ hex: undefined,
72
+ blob: undefined,
73
+ }))();
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,11 @@
1
+ export declare class CryptoHelpers {
2
+ static secretBytes(length: number): Promise<Buffer | Uint8Array>;
3
+ static base16secret(length: number): Promise<string>;
4
+ static base62secret(length: number): Promise<string>;
5
+ static getHash(algorithm: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512', input: string | ArrayBufferLike): Promise<string>;
6
+ /**
7
+ * @returns Fully formatted (double quote encapsulated) `ETag` header value
8
+ * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#etag_value
9
+ */
10
+ static generateETag(response: Response, algorithm?: Parameters<typeof this.getHash>[0]): Promise<string>;
11
+ }
@@ -0,0 +1,56 @@
1
+ import { BufferHelpers } from './buffers.mjs';
2
+ export class CryptoHelpers {
3
+ static secretBytes(length) {
4
+ return import('node:crypto')
5
+ .then(({ randomBytes }) => randomBytes(length))
6
+ .catch(() => {
7
+ const randomBytes = new Uint8Array(length);
8
+ crypto.getRandomValues(randomBytes);
9
+ return randomBytes;
10
+ });
11
+ }
12
+ static base16secret(length) {
13
+ return this.secretBytes(length).then((bytes) => BufferHelpers.bufferToHex(bytes));
14
+ }
15
+ static base62secret(length) {
16
+ const LOWER_CHAR_SET = 'abcdefghijklmnopqrstuvwxyz';
17
+ const NUMBER_CHAR_SET = '0123456789';
18
+ const CHAR_SET = `${NUMBER_CHAR_SET}${LOWER_CHAR_SET}${LOWER_CHAR_SET.toUpperCase()}`;
19
+ return this.secretBytes(length).then((randomBytes) => {
20
+ /**
21
+ * @link https://jsbm.dev/x1F2ITy7RU8T2
22
+ */
23
+ let randomText = '';
24
+ for (const byte of randomBytes) {
25
+ // Map each byte to a character in the character set
26
+ const charIndex = byte % CHAR_SET.length;
27
+ randomText += CHAR_SET.charAt(charIndex);
28
+ }
29
+ return randomText;
30
+ });
31
+ }
32
+ static getHash(algorithm, input) {
33
+ return import('node:crypto')
34
+ .then(async ({ createHash }) => {
35
+ const hash = createHash(algorithm.replace('-', '').toLowerCase());
36
+ if (typeof input === 'string') {
37
+ hash.update(input);
38
+ }
39
+ else {
40
+ await import('node:buffer').then(({ Buffer }) => hash.update(Buffer.from(input)));
41
+ }
42
+ return hash.digest('hex');
43
+ })
44
+ .catch(() => crypto.subtle.digest(algorithm, typeof input === 'string' ? new TextEncoder().encode(input) : input).then((hashBuffer) => BufferHelpers.bufferToHex(hashBuffer)));
45
+ }
46
+ /**
47
+ * @returns Fully formatted (double quote encapsulated) `ETag` header value
48
+ * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#etag_value
49
+ */
50
+ static generateETag(response, algorithm = 'SHA-512') {
51
+ return response
52
+ .clone()
53
+ .arrayBuffer()
54
+ .then((buffer) => this.getHash(algorithm, buffer).then((hex) => `"${hex}"`));
55
+ }
56
+ }
@@ -0,0 +1,14 @@
1
+ export * from './buffers.mjs';
2
+ export * from './crypto.mjs';
3
+ export * from './net.mjs';
4
+ export declare class Helpers {
5
+ static precisionFloat(input: string): number;
6
+ /**
7
+ * A wrapper around `Promise.allSettled()` that filters and returns only the fulfilled results. This method behaves like `Promise.allSettled()` where one promise failing doesn't stop others.
8
+ * However, like `Promise.all()`, it only returns the values of successfully fulfilled promises without needing to manually check their status.
9
+ *
10
+ * @param promises - An array of promises to be settled.
11
+ * @returns A promise that resolves to an array of fulfilled values from the input promises.
12
+ */
13
+ static getFulfilledResults<T extends unknown>(promises: PromiseLike<T>[]): Promise<Awaited<T>[]>;
14
+ }
package/dist/index.mjs ADDED
@@ -0,0 +1,31 @@
1
+ export * from './buffers.mjs';
2
+ export * from './crypto.mjs';
3
+ export * from './net.mjs';
4
+ export class Helpers {
5
+ static precisionFloat(input) {
6
+ if (!input.includes('.')) {
7
+ // No decimal point means it's an integer, just return as a float
8
+ return parseFloat(input);
9
+ }
10
+ else {
11
+ if (input.endsWith('0')) {
12
+ // Replace the last '0' with '1' while keeping other characters unchanged
13
+ return parseFloat(input.substring(0, input.length - 1) + '1');
14
+ }
15
+ else {
16
+ // If last decimal is not zero, parse as usual
17
+ return parseFloat(input);
18
+ }
19
+ }
20
+ }
21
+ /**
22
+ * A wrapper around `Promise.allSettled()` that filters and returns only the fulfilled results. This method behaves like `Promise.allSettled()` where one promise failing doesn't stop others.
23
+ * However, like `Promise.all()`, it only returns the values of successfully fulfilled promises without needing to manually check their status.
24
+ *
25
+ * @param promises - An array of promises to be settled.
26
+ * @returns A promise that resolves to an array of fulfilled values from the input promises.
27
+ */
28
+ static getFulfilledResults(promises) {
29
+ return Promise.allSettled(promises).then((results) => results.filter((result) => result.status === 'fulfilled').map((result) => result.value));
30
+ }
31
+ }
package/dist/net.d.mts ADDED
@@ -0,0 +1,22 @@
1
+ import type { CustomLoging } from '@chainfuse/types';
2
+ export declare class NetHelpers {
3
+ /**
4
+ * Removes the `body` property from a RequestInit object to reduce verbosity when logging.
5
+ *
6
+ * @param {RequestInit} [init={}] - The RequestInit object from which to remove the 'body' property. If not provided, an empty object will be used.
7
+ *
8
+ * @returns {RequestInit} The updated RequestInit object without the 'body' property.
9
+ */
10
+ static initBodyTrimmer(init?: RequestInit): RequestInit;
11
+ static stripSensitiveHeaders(originalHeaders?: Headers): Headers;
12
+ static cfApi(apiKey: string, cacheTtl?: number, logger?: CustomLoging): Promise<import("cloudflare").Cloudflare>;
13
+ static loggingFetch(info: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1], body?: boolean, logger?: CustomLoging): Promise<Response>;
14
+ /**
15
+ * Parses the Server-Timing header and returns an object with the metrics.
16
+ * The object keys are the metric names (with optional descriptions), and the values are the duration of each metric or null if no duration is found.
17
+ *
18
+ * @param {string} [serverTimingHeader=''] - The Server-Timing header string.
19
+ * @returns {Record<string, number | null>} An object where keys are metric names (with optional descriptions) and values are the durations in milliseconds or null.
20
+ */
21
+ static serverTiming(serverTimingHeader?: string): Record<string, number | null>;
22
+ }
package/dist/net.mjs ADDED
@@ -0,0 +1,280 @@
1
+ import { CryptoHelpers } from './crypto.mjs';
2
+ export class NetHelpers {
3
+ /**
4
+ * Removes the `body` property from a RequestInit object to reduce verbosity when logging.
5
+ *
6
+ * @param {RequestInit} [init={}] - The RequestInit object from which to remove the 'body' property. If not provided, an empty object will be used.
7
+ *
8
+ * @returns {RequestInit} The updated RequestInit object without the 'body' property.
9
+ */
10
+ static initBodyTrimmer(init = {}) {
11
+ if ('cf' in init)
12
+ delete init.cf;
13
+ delete init.body;
14
+ return init;
15
+ }
16
+ static stripSensitiveHeaders(originalHeaders = new Headers()) {
17
+ originalHeaders.delete('Set-Cookie');
18
+ originalHeaders.delete('Authorization');
19
+ return originalHeaders;
20
+ }
21
+ static cfApi(apiKey, cacheTtl, logger = false) {
22
+ return import('cloudflare').then(({ Cloudflare }) => new Cloudflare({
23
+ apiToken: apiKey,
24
+ fetch: (info, init) =>
25
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
26
+ new Promise(async (resolve) => {
27
+ const cacheKey = new Request(info, { ...init, headers: this.stripSensitiveHeaders(new Headers(init?.headers)) });
28
+ if (typeof logger === 'boolean') {
29
+ if (logger) {
30
+ await import('chalk')
31
+ .then(({ Chalk }) => {
32
+ const chalk = new Chalk({ level: 1 });
33
+ console.debug(chalk.magenta('CF Fetch request'), chalk.magenta(cacheKey.url), JSON.stringify(this.initBodyTrimmer({ ...init, headers: Object.fromEntries(this.stripSensitiveHeaders(new Headers(init?.headers)).entries()) }), null, '\t'));
34
+ })
35
+ .catch(() => console.debug('CF Fetch request', cacheKey.url, JSON.stringify(this.initBodyTrimmer({ ...init, headers: Object.fromEntries(this.stripSensitiveHeaders(new Headers(init?.headers)).entries()) }), null, '\t')));
36
+ }
37
+ }
38
+ else {
39
+ logger(`CF Fetch request ${cacheKey.url} ${JSON.stringify(this.initBodyTrimmer({ ...init, headers: Object.fromEntries(this.stripSensitiveHeaders(new Headers(init?.headers)).entries()) }), null, '\t')}`);
40
+ }
41
+ if (cacheTtl) {
42
+ const cfCacheRef = caches?.open('cfApi');
43
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
44
+ if (cfCacheRef) {
45
+ await cfCacheRef
46
+ .then((cfCache) => cfCache
47
+ .match(cacheKey)
48
+ .then(async (cachedResponse) => {
49
+ if (cachedResponse) {
50
+ if (typeof logger === 'boolean') {
51
+ if (logger) {
52
+ await import('chalk')
53
+ .then(({ Chalk }) => {
54
+ const chalk = new Chalk({ level: 1 });
55
+ console.debug(chalk.green('CF Cache hit'), cacheKey.url);
56
+ })
57
+ .catch(() => console.debug('CF Cache hit', cacheKey.url));
58
+ }
59
+ }
60
+ else {
61
+ logger(`CF Cache hit ${cacheKey.url}`);
62
+ }
63
+ if (cachedResponse.status < 500) {
64
+ resolve(cachedResponse);
65
+ }
66
+ else {
67
+ void cfCache.delete(cacheKey).then(() => resolve(cachedResponse));
68
+ }
69
+ }
70
+ else {
71
+ if (typeof logger === 'boolean') {
72
+ if (logger) {
73
+ await import('chalk')
74
+ .then(({ Chalk }) => {
75
+ const chalk = new Chalk({ level: 1 });
76
+ console.warn(chalk.yellow('CF Cache missed'), cacheKey.url);
77
+ })
78
+ .catch(() => console.warn('CF Cache missed', cacheKey.url));
79
+ }
80
+ }
81
+ else {
82
+ logger(`CF Cache missed ${cacheKey.url}`);
83
+ }
84
+ await this.loggingFetch(info, init, undefined, logger)
85
+ .then((response) => {
86
+ resolve(response.clone());
87
+ return response;
88
+ })
89
+ .then((response) => {
90
+ if (response.status < 500) {
91
+ response = new Response(response.body, response);
92
+ response.headers.set('Cache-Control', `s-maxage=${cacheTtl}`);
93
+ if (response.headers.has('ETag')) {
94
+ return cfCache.put(cacheKey, response).then(async () => {
95
+ if (typeof logger === 'boolean') {
96
+ if (logger) {
97
+ await import('chalk')
98
+ .then(({ Chalk }) => {
99
+ const chalk = new Chalk({ level: 1 });
100
+ console.debug(chalk.gray('CF Cache saved'), cacheKey.url);
101
+ })
102
+ .catch(() => console.debug('CF Cache saved', cacheKey.url));
103
+ }
104
+ }
105
+ else {
106
+ logger(`CF Cache saved ${cacheKey.url}`);
107
+ }
108
+ });
109
+ }
110
+ else {
111
+ return (CryptoHelpers.generateETag(response)
112
+ .then((etag) => response.headers.set('ETag', etag))
113
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
114
+ .finally(() => cfCache.put(cacheKey, response).then(async () => {
115
+ if (typeof logger === 'boolean') {
116
+ if (logger) {
117
+ await import('chalk')
118
+ .then(({ Chalk }) => {
119
+ const chalk = new Chalk({ level: 1 });
120
+ console.debug(chalk.gray('CF Cache saved'), cacheKey.url);
121
+ })
122
+ .catch(() => console.debug('CF Cache saved', cacheKey.url));
123
+ }
124
+ }
125
+ else {
126
+ logger(`CF Cache saved ${cacheKey.url}`);
127
+ }
128
+ })));
129
+ }
130
+ }
131
+ else {
132
+ return;
133
+ }
134
+ });
135
+ }
136
+ })
137
+ .catch(async (error) => {
138
+ if (typeof logger === 'boolean') {
139
+ if (logger) {
140
+ await import('chalk')
141
+ .then(({ Chalk }) => {
142
+ const chalk = new Chalk({ level: 1 });
143
+ console.error(chalk.red('CF Cache match error'), error);
144
+ })
145
+ .catch(() => console.error('CF Cache match error', error));
146
+ }
147
+ }
148
+ else {
149
+ logger(`CF Cache match error ${error}`);
150
+ }
151
+ resolve(this.loggingFetch(info, init, undefined, logger));
152
+ }))
153
+ .catch(async (error) => {
154
+ if (typeof logger === 'boolean') {
155
+ if (logger) {
156
+ await import('chalk')
157
+ .then(({ Chalk }) => {
158
+ const chalk = new Chalk({ level: 1 });
159
+ console.error(chalk.red('CF Cache open error'), error);
160
+ })
161
+ .catch(() => console.error('CF Cache open error', error));
162
+ }
163
+ }
164
+ else {
165
+ logger(`CF Cache open error ${error}`);
166
+ }
167
+ resolve(this.loggingFetch(info, init, undefined, logger));
168
+ });
169
+ }
170
+ else {
171
+ if (typeof logger === 'boolean') {
172
+ if (logger) {
173
+ await import('chalk')
174
+ .then(({ Chalk }) => {
175
+ const chalk = new Chalk({ level: 1 });
176
+ console.warn(chalk.yellow('CF Cache not available'));
177
+ })
178
+ .catch(() => console.warn('CF Cache not available'));
179
+ }
180
+ }
181
+ else {
182
+ logger('CF Cache not available');
183
+ }
184
+ resolve(this.loggingFetch(info, init, undefined, logger));
185
+ }
186
+ }
187
+ else {
188
+ if (typeof logger === 'boolean') {
189
+ if (logger) {
190
+ await import('chalk')
191
+ .then(({ Chalk }) => {
192
+ const chalk = new Chalk({ level: 1 });
193
+ console.warn(chalk.yellow('CF Cache ignored'), cacheKey.url);
194
+ })
195
+ .catch(() => console.warn('CF Cache ignored', cacheKey.url));
196
+ }
197
+ }
198
+ else {
199
+ logger(`CF Cache ignored ${cacheKey.url}`);
200
+ }
201
+ resolve(this.loggingFetch(info, init, undefined, logger));
202
+ }
203
+ }),
204
+ }));
205
+ }
206
+ static loggingFetch(info, init, body = false, logger = false) {
207
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
208
+ return new Promise((resolve, reject) => fetch(info, init)
209
+ .then(async (response) => {
210
+ resolve(response.clone());
211
+ // Allow mutable headers
212
+ response = new Response(response.body, response);
213
+ const loggingContent = {
214
+ headers: Object.fromEntries(this.stripSensitiveHeaders(response.headers).entries()),
215
+ status: response.status,
216
+ statusText: response.statusText,
217
+ ok: response.ok,
218
+ type: response.type,
219
+ };
220
+ if (body) {
221
+ if (response.headers.get('Content-Type')?.toLowerCase().startsWith('application/json')) {
222
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
223
+ loggingContent['body'] = await response.json();
224
+ }
225
+ else {
226
+ loggingContent['body'] = await response.text();
227
+ }
228
+ }
229
+ if (typeof logger === 'boolean') {
230
+ if (logger) {
231
+ await import('chalk')
232
+ .then(({ Chalk }) => {
233
+ const chalk = new Chalk({ level: 1 });
234
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
235
+ console.debug(response.ok ? chalk.green('Fetch response') : chalk.red('Fetch response'), response.ok, response.ok ? chalk.green(response.url || info.toString()) : chalk.red(response.url || info.toString()), JSON.stringify(loggingContent, null, '\t'));
236
+ })
237
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
238
+ .catch(() => console.debug('Fetch response', response.ok, response.url || info.toString(), JSON.stringify(loggingContent, null, '\t')));
239
+ }
240
+ }
241
+ else {
242
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
243
+ logger(`Fetch response ${response.ok} ${response.url || info.toString()} ${JSON.stringify(loggingContent, null, '\t')}`);
244
+ }
245
+ })
246
+ .catch(reject));
247
+ }
248
+ /**
249
+ * Parses the Server-Timing header and returns an object with the metrics.
250
+ * The object keys are the metric names (with optional descriptions), and the values are the duration of each metric or null if no duration is found.
251
+ *
252
+ * @param {string} [serverTimingHeader=''] - The Server-Timing header string.
253
+ * @returns {Record<string, number | null>} An object where keys are metric names (with optional descriptions) and values are the durations in milliseconds or null.
254
+ */
255
+ static serverTiming(serverTimingHeader = '') {
256
+ const result = {};
257
+ if (serverTimingHeader && serverTimingHeader.trim().length > 0) {
258
+ // Split the header by comma to get each metric
259
+ const metrics = serverTimingHeader.trim().split(',');
260
+ metrics.forEach((metric) => {
261
+ // Split each metric by semicolon to separate the name from other attributes
262
+ const parts = metric.split(';').map((part) => part.trim());
263
+ // Get the metric name
264
+ const name = parts[0];
265
+ // Find the 'dur' attribute and convert it to a number
266
+ const durationPart = parts.find((part) => part.startsWith('dur='));
267
+ const duration = durationPart ? parseFloat(durationPart.split('=')[1]) : null;
268
+ // Optionally find the 'desc' attribute
269
+ const descriptionPart = parts.find((part) => part.startsWith('desc='));
270
+ const description = descriptionPart ? descriptionPart.split('=')[1] : null;
271
+ // Construct the key name with optional description
272
+ const keyName = description ? `${name} (${description})` : name;
273
+ if (name) {
274
+ result[keyName] = duration;
275
+ }
276
+ });
277
+ }
278
+ return result;
279
+ }
280
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@chainfuse/helpers",
3
+ "version": "0.0.2",
4
+ "description": "",
5
+ "author": "ChainFuse",
6
+ "homepage": "https://github.com/ChainFuse/packages/tree/main/packages/helpers#readme",
7
+ "license": "Apache-2.0",
8
+ "main": "./dist/index.mjs",
9
+ "directories": {
10
+ "lib": "dist",
11
+ "test": "__tests__"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ChainFuse/packages.git"
22
+ },
23
+ "scripts": {
24
+ "fmt": "prettier --check .",
25
+ "fmt:fix": "prettier --write .",
26
+ "lint": "eslint .",
27
+ "lint:fix": "npm run lint -- --fix",
28
+ "build": "tsc",
29
+ "build:clean": "npm run build -- --build --clean && npm run build",
30
+ "pretest": "tsc --project tsconfig.tests.json",
31
+ "test": "node --enable-source-maps --test --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout",
32
+ "test:local": "npm run test"
33
+ },
34
+ "type": "module",
35
+ "bugs": {
36
+ "url": "https://github.com/ChainFuse/packages/issues"
37
+ },
38
+ "types": "./dist/index.d.mts",
39
+ "engines": {
40
+ "node": ">=20.16.0"
41
+ },
42
+ "exports": {
43
+ ".": {
44
+ "import": "./dist/index.mjs",
45
+ "types": "./dist/index.d.mts"
46
+ }
47
+ },
48
+ "prettier": "@demosjarco/prettier-config",
49
+ "dependencies": {
50
+ "chalk": "^5.3.0",
51
+ "cloudflare": "^3.5.0",
52
+ "uuid": "^10.0.0"
53
+ },
54
+ "devDependencies": {
55
+ "@chainfuse/types": "^0.0.2",
56
+ "@types/node": "^22.5.1",
57
+ "@types/uuid": "^10.0.0"
58
+ },
59
+ "gitHead": "46683b9763bb38416cc19e46dda479814d6a4c14"
60
+ }