@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.
@@ -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
+ }
@@ -1,3 +1,4 @@
1
+ export * from './batch-writer.js';
1
2
  export * from './perform-in-chunks.js';
2
3
  export * from './perform-in-time-chunks.js';
3
4
  export * from './replicate.js';
@@ -1,3 +1,4 @@
1
+ export * from './batch-writer.js';
1
2
  export * from './perform-in-chunks.js';
2
3
  export * from './perform-in-time-chunks.js';
3
4
  export * from './replicate.js';
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmlmobilidade/utils",
3
- "version": "20260330.1756.23",
3
+ "version": "20260331.1620.53",
4
4
  "author": {
5
5
  "email": "iso@tmlmobilidade.pt",
6
6
  "name": "TML-ISO"