@tmlmobilidade/utils 20260330.1756.23 → 20260331.1620.53
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/batching/batch-writer.d.ts +81 -0
- package/dist/batching/batch-writer.js +166 -0
- package/dist/batching/index.d.ts +1 -0
- package/dist/batching/index.js +1 -0
- package/dist/http.d.ts +10 -0
- package/dist/http.js +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
interface BatchWriterParams<T> {
|
|
2
|
+
/**
|
|
3
|
+
* The maximum number of items to hold in memory
|
|
4
|
+
* before flushing to the database.
|
|
5
|
+
* @required
|
|
6
|
+
*/
|
|
7
|
+
batch_size: number;
|
|
8
|
+
/**
|
|
9
|
+
* How long, in milliseconds, data should be kept in memory before
|
|
10
|
+
* flushing to the database. If this feature is enabled, a flush will
|
|
11
|
+
* be triggered even if the batch is not full. Disabled by default.
|
|
12
|
+
* @default disabled
|
|
13
|
+
*/
|
|
14
|
+
batch_timeout?: number;
|
|
15
|
+
/**
|
|
16
|
+
* How long to wait, in milliseconds, after the last write operation
|
|
17
|
+
* before flushing the data to the database. This can be used to prevent
|
|
18
|
+
* items staying in memory for too long if the batch size is not reached
|
|
19
|
+
* frequently enough. Disabled by default.
|
|
20
|
+
* @default disabled
|
|
21
|
+
*/
|
|
22
|
+
idle_timeout?: number;
|
|
23
|
+
/**
|
|
24
|
+
* The insert function to use for inserting data into the batch.
|
|
25
|
+
* @required
|
|
26
|
+
*/
|
|
27
|
+
insertFn: (data: T[]) => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Maximum number of retries for transient insert errors.
|
|
30
|
+
* @default 3
|
|
31
|
+
*/
|
|
32
|
+
max_retries?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Base delay in milliseconds for exponential backoff.
|
|
35
|
+
* @default 1000
|
|
36
|
+
*/
|
|
37
|
+
retry_base_delay_ms?: number;
|
|
38
|
+
/**
|
|
39
|
+
* The title of this BatchWriter instance,
|
|
40
|
+
* used to identify the source of the logs.
|
|
41
|
+
* @required
|
|
42
|
+
*/
|
|
43
|
+
title: string;
|
|
44
|
+
}
|
|
45
|
+
export declare class BatchWriter<T> {
|
|
46
|
+
private params;
|
|
47
|
+
private dataBucketAlwaysAvailable;
|
|
48
|
+
private dataBucketFlushOps;
|
|
49
|
+
private batchTimeoutTimer;
|
|
50
|
+
private idleTimeoutTimer;
|
|
51
|
+
private sessionTimer;
|
|
52
|
+
constructor(params: BatchWriterParams<T>);
|
|
53
|
+
/**
|
|
54
|
+
* Flushes the current batch of data.
|
|
55
|
+
* This method is called internally when the batch size or timeouts are reached,
|
|
56
|
+
* but can also be called manually if needed.
|
|
57
|
+
* @param callback Optional callback to execute after the flush is complete, receiving the flushed data as a parameter
|
|
58
|
+
*/
|
|
59
|
+
flush(callback?: (data?: T[]) => Promise<void>): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Helper method to perform insert operations with retry logic for transient errors.
|
|
62
|
+
* This method will attempt to insert the data using the provided insert function,
|
|
63
|
+
* and if an error occurs, it will retry the operation with exponential backoff
|
|
64
|
+
* until the maximum number of retries is reached.
|
|
65
|
+
* @param data The data to insert.
|
|
66
|
+
* @returns A promise that resolves when the insert operation is successful, or rejects if all retries fail.
|
|
67
|
+
*/
|
|
68
|
+
private insertWithRetry;
|
|
69
|
+
/**
|
|
70
|
+
* Write data to the batch.
|
|
71
|
+
* @param data The data to write.
|
|
72
|
+
* @param options Options for the write operation (reserved for future use).
|
|
73
|
+
* @param writeCallback Callback function to call after the write operation is complete.
|
|
74
|
+
* @param flushCallback Callback function to call after the flush operation is complete.
|
|
75
|
+
*/
|
|
76
|
+
write(data: T | T[], { flushCallback, writeCallback }?: {
|
|
77
|
+
flushCallback?: (data?: T[]) => Promise<void>;
|
|
78
|
+
writeCallback?: () => Promise<void>;
|
|
79
|
+
}): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* eslint-disable perfectionist/sort-classes */
|
|
2
|
+
/* * */
|
|
3
|
+
import { Logger } from '@tmlmobilidade/logger';
|
|
4
|
+
import { Timer } from '@tmlmobilidade/timer';
|
|
5
|
+
/* * */
|
|
6
|
+
export class BatchWriter {
|
|
7
|
+
//
|
|
8
|
+
params;
|
|
9
|
+
dataBucketAlwaysAvailable = [];
|
|
10
|
+
dataBucketFlushOps = [];
|
|
11
|
+
batchTimeoutTimer = null;
|
|
12
|
+
idleTimeoutTimer = null;
|
|
13
|
+
sessionTimer = new Timer();
|
|
14
|
+
constructor(params) {
|
|
15
|
+
if (!params.title)
|
|
16
|
+
throw new Error('BATCHWRITER: Title is required.');
|
|
17
|
+
if (!params.insertFn)
|
|
18
|
+
throw new Error('BATCHWRITER: Insert function is required.');
|
|
19
|
+
if (!params.batch_size)
|
|
20
|
+
throw new Error('BATCHWRITER: Batch size is required.');
|
|
21
|
+
this.params = params;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Flushes the current batch of data.
|
|
25
|
+
* This method is called internally when the batch size or timeouts are reached,
|
|
26
|
+
* but can also be called manually if needed.
|
|
27
|
+
* @param callback Optional callback to execute after the flush is complete, receiving the flushed data as a parameter
|
|
28
|
+
*/
|
|
29
|
+
async flush(callback) {
|
|
30
|
+
try {
|
|
31
|
+
//
|
|
32
|
+
const flushTimer = new Timer();
|
|
33
|
+
const sessionTimerResult = this.sessionTimer.get();
|
|
34
|
+
//
|
|
35
|
+
// Invalidate all timers since a flush operation is being performed
|
|
36
|
+
if (this.idleTimeoutTimer) {
|
|
37
|
+
clearTimeout(this.idleTimeoutTimer);
|
|
38
|
+
this.idleTimeoutTimer = null;
|
|
39
|
+
}
|
|
40
|
+
if (this.batchTimeoutTimer) {
|
|
41
|
+
clearTimeout(this.batchTimeoutTimer);
|
|
42
|
+
this.batchTimeoutTimer = null;
|
|
43
|
+
}
|
|
44
|
+
//
|
|
45
|
+
// Skip if there is no data to flush
|
|
46
|
+
if (this.dataBucketAlwaysAvailable.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
//
|
|
49
|
+
// Copy everything in dataBucketAlwaysAvailable to dataBucketFlushOps
|
|
50
|
+
// to prevent any new incoming data to be added to the batch. This is to ensure
|
|
51
|
+
// that the batch is not modified while it is being processed.
|
|
52
|
+
this.dataBucketFlushOps = [...this.dataBucketFlushOps, ...this.dataBucketAlwaysAvailable];
|
|
53
|
+
this.dataBucketAlwaysAvailable = [];
|
|
54
|
+
//
|
|
55
|
+
// Process the data for batch insert
|
|
56
|
+
try {
|
|
57
|
+
// Call the insert function provided in the params to perform the actual database insertion.
|
|
58
|
+
if (!this.params.insertFn)
|
|
59
|
+
throw new Error('BATCHWRITER: No insert function provided in params');
|
|
60
|
+
await this.insertWithRetry(this.dataBucketFlushOps);
|
|
61
|
+
Logger.info(`BATCHWRITER [${this.params.title}]: Flush | Length: ${this.dataBucketFlushOps.length} (session: ${sessionTimerResult}) (flush: ${flushTimer.get()})`);
|
|
62
|
+
// Call the flush callback, if provided
|
|
63
|
+
if (callback)
|
|
64
|
+
await callback(this.dataBucketFlushOps);
|
|
65
|
+
// Reset the flush bucket
|
|
66
|
+
this.dataBucketFlushOps = [];
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
Logger.error(`BATCHWRITER [${this.params.title}]: Error @ flush().insert(): ${error.message}`);
|
|
70
|
+
throw error; // Re-throw to allow retry logic at higher level
|
|
71
|
+
}
|
|
72
|
+
//
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
Logger.error(`BATCHWRITER [${this.params.title}]: Error @ flush(): ${error.message}`);
|
|
76
|
+
throw error; // Re-throw to allow retry logic at higher level
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Helper method to perform insert operations with retry logic for transient errors.
|
|
81
|
+
* This method will attempt to insert the data using the provided insert function,
|
|
82
|
+
* and if an error occurs, it will retry the operation with exponential backoff
|
|
83
|
+
* until the maximum number of retries is reached.
|
|
84
|
+
* @param data The data to insert.
|
|
85
|
+
* @returns A promise that resolves when the insert operation is successful, or rejects if all retries fail.
|
|
86
|
+
*/
|
|
87
|
+
async insertWithRetry(data) {
|
|
88
|
+
const maxRetries = this.params.max_retries ?? 3;
|
|
89
|
+
const retryBaseDelayMs = this.params.retry_base_delay_ms ?? 1000;
|
|
90
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
91
|
+
try {
|
|
92
|
+
await this.params.insertFn(data);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const parsedError = error;
|
|
97
|
+
const nextAttempt = attempt + 1;
|
|
98
|
+
const delayMs = retryBaseDelayMs * (2 ** attempt);
|
|
99
|
+
Logger.error(`BATCHWRITER [${this.params.title}]: Transient insert error (${parsedError.code ?? 'unknown'}). Retrying ${nextAttempt}/${maxRetries} in ${delayMs}ms. ${parsedError.message}`);
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Write data to the batch.
|
|
106
|
+
* @param data The data to write.
|
|
107
|
+
* @param options Options for the write operation (reserved for future use).
|
|
108
|
+
* @param writeCallback Callback function to call after the write operation is complete.
|
|
109
|
+
* @param flushCallback Callback function to call after the flush operation is complete.
|
|
110
|
+
*/
|
|
111
|
+
async write(data, { flushCallback, writeCallback } = {}) {
|
|
112
|
+
//
|
|
113
|
+
//
|
|
114
|
+
// Invalidate the previously set idle timeout timer
|
|
115
|
+
// since we are performing a write operation again.
|
|
116
|
+
if (this.idleTimeoutTimer) {
|
|
117
|
+
clearTimeout(this.idleTimeoutTimer);
|
|
118
|
+
this.idleTimeoutTimer = null;
|
|
119
|
+
}
|
|
120
|
+
//
|
|
121
|
+
// Check if the batch is full
|
|
122
|
+
const batchSize = this.params.batch_size ?? 10_000;
|
|
123
|
+
if (this.dataBucketAlwaysAvailable.length >= batchSize) {
|
|
124
|
+
Logger.info(`BATCHWRITER [${this.params.title}]: Batch full. Flushing data...`);
|
|
125
|
+
await this.flush(flushCallback);
|
|
126
|
+
}
|
|
127
|
+
//
|
|
128
|
+
// Reset the session timer (for logging purposes)
|
|
129
|
+
if (this.dataBucketAlwaysAvailable.length === 0) {
|
|
130
|
+
this.sessionTimer.reset();
|
|
131
|
+
}
|
|
132
|
+
//
|
|
133
|
+
// Add the current data to the batch
|
|
134
|
+
if (Array.isArray(data)) {
|
|
135
|
+
const combinedDataWithOptions = data.map(item => item);
|
|
136
|
+
this.dataBucketAlwaysAvailable = [...this.dataBucketAlwaysAvailable, ...combinedDataWithOptions];
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.dataBucketAlwaysAvailable.push(data);
|
|
140
|
+
}
|
|
141
|
+
//
|
|
142
|
+
// Call the write callback, if provided
|
|
143
|
+
if (writeCallback) {
|
|
144
|
+
await writeCallback();
|
|
145
|
+
}
|
|
146
|
+
//
|
|
147
|
+
// Setup the idle timeout timer to flush the data if too long has passed
|
|
148
|
+
// since the last write operation. Check if this functionality is enabled.
|
|
149
|
+
if (this.params.idle_timeout && this.params.idle_timeout > 0 && !this.idleTimeoutTimer) {
|
|
150
|
+
this.idleTimeoutTimer = setTimeout(async () => {
|
|
151
|
+
Logger.info(`BATCHWRITER [${this.params.title}]: Idle timeout reached. Flushing data...`);
|
|
152
|
+
await this.flush(flushCallback);
|
|
153
|
+
}, this.params.idle_timeout);
|
|
154
|
+
}
|
|
155
|
+
//
|
|
156
|
+
// Setup the batch timeout timer to flush the data, if the timeout value is reached,
|
|
157
|
+
// even if the batch is not full. Check if this functionality is enabled.
|
|
158
|
+
if (this.params.batch_timeout && this.params.batch_timeout > 0 && !this.batchTimeoutTimer) {
|
|
159
|
+
this.batchTimeoutTimer = setTimeout(async () => {
|
|
160
|
+
Logger.info(`BATCHWRITER [${this.params.title}]: Batch timeout reached. Flushing data...`);
|
|
161
|
+
await this.flush(flushCallback);
|
|
162
|
+
}, this.params.batch_timeout);
|
|
163
|
+
}
|
|
164
|
+
//
|
|
165
|
+
}
|
|
166
|
+
}
|
package/dist/batching/index.d.ts
CHANGED
package/dist/batching/index.js
CHANGED
package/dist/http.d.ts
CHANGED
|
@@ -73,6 +73,16 @@ export declare function uploadFile<T>(url: string, file: File): Promise<HttpResp
|
|
|
73
73
|
* ```
|
|
74
74
|
*/
|
|
75
75
|
export declare const swrFetcher: <T>(url: string) => Promise<T>;
|
|
76
|
+
/**
|
|
77
|
+
* Fetches data from a URL using the SWR fetcher function.
|
|
78
|
+
* @param url - The URL to fetch from
|
|
79
|
+
* @returns Promise resolving to the fetched data
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const data = await swrFetcher('/api/users/123');
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare const unauthenticatedSwrFetcher: <T>(url: string) => Promise<T>;
|
|
76
86
|
/**
|
|
77
87
|
* Fetches data from a URL using the SWR fetcher function without authentication.
|
|
78
88
|
* @param url - The URL to fetch from
|
package/dist/http.js
CHANGED
|
@@ -160,6 +160,23 @@ export const swrFetcher = async (url) => {
|
|
|
160
160
|
}
|
|
161
161
|
return data.data;
|
|
162
162
|
};
|
|
163
|
+
/**
|
|
164
|
+
* Fetches data from a URL using the SWR fetcher function.
|
|
165
|
+
* @param url - The URL to fetch from
|
|
166
|
+
* @returns Promise resolving to the fetched data
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* const data = await swrFetcher('/api/users/123');
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export const unauthenticatedSwrFetcher = async (url) => {
|
|
173
|
+
const res = await fetch(url, { credentials: 'omit' });
|
|
174
|
+
const data = await res.json();
|
|
175
|
+
if (!res.ok) {
|
|
176
|
+
throw new HttpException(res.status, data.error ?? 'An error occurred');
|
|
177
|
+
}
|
|
178
|
+
return data.data;
|
|
179
|
+
};
|
|
163
180
|
/**
|
|
164
181
|
* Fetches data from a URL using the SWR fetcher function without authentication.
|
|
165
182
|
* @param url - The URL to fetch from
|