@synode/adapter-http 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,35 @@
1
+ Synode Proprietary License
2
+
3
+ Copyright (c) 2026 Digitl Cloud GmbH. All rights reserved.
4
+
5
+ Permission is hereby granted, free of charge, to any person or organization
6
+ obtaining a copy of this software and associated documentation files (the
7
+ "Software"), to use the Software for personal, internal, and commercial
8
+ purposes, subject to the following conditions:
9
+
10
+ 1. PERMITTED USE. You may use, copy, and modify the Software for your own
11
+ personal, internal, or commercial purposes.
12
+
13
+ 2. NO REDISTRIBUTION. You may not distribute, publish, sublicense, or
14
+ otherwise make the Software or any derivative works available to third
15
+ parties, whether in source code or compiled form, free of charge or for
16
+ a fee.
17
+
18
+ 3. NO RESALE. You may not sell, rent, lease, or otherwise commercially
19
+ exploit the Software itself as a standalone product or as part of a
20
+ software distribution.
21
+
22
+ 4. NO HOSTING AS A SERVICE. You may not offer the Software to third parties
23
+ as a hosted, managed, or software-as-a-service product where the primary
24
+ value derives from the Software.
25
+
26
+ 5. ATTRIBUTION. You must retain this license notice and copyright notice in
27
+ all copies or substantial portions of the Software.
28
+
29
+ 6. NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
30
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
32
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES
33
+ OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
34
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
35
+ OTHER DEALINGS IN THE SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,88 @@
1
+
2
+ //#region src/index.ts
3
+ /**
4
+ * Adapter that sends events to an HTTP endpoint with batching and retry.
5
+ *
6
+ * Events are buffered until `batchSize` is reached, then sent as a JSON payload.
7
+ * An optional `flushInterval` timer flushes partial batches periodically.
8
+ * Retries with exponential backoff on 5xx and 429 responses.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const adapter = new HttpAdapter({
13
+ * url: 'https://api.example.com/events',
14
+ * batchSize: 10,
15
+ * headers: { Authorization: 'Bearer token' },
16
+ * });
17
+ * await generate(journey, { users: 50, adapter });
18
+ * await adapter.close();
19
+ * ```
20
+ */
21
+ var HttpAdapter = class {
22
+ url;
23
+ method;
24
+ headers;
25
+ batchSize;
26
+ flushInterval;
27
+ maxRetries;
28
+ transform;
29
+ buffer = [];
30
+ timer = null;
31
+ constructor(options) {
32
+ this.url = options.url;
33
+ this.method = options.method ?? "POST";
34
+ this.headers = {
35
+ "Content-Type": "application/json",
36
+ ...options.headers
37
+ };
38
+ this.batchSize = options.batchSize ?? 1;
39
+ this.flushInterval = options.flushInterval ?? 5e3;
40
+ this.maxRetries = options.maxRetries ?? 3;
41
+ this.transform = options.transform;
42
+ if (this.flushInterval > 0) this.timer = setInterval(() => {
43
+ if (this.buffer.length > 0) this.flush();
44
+ }, this.flushInterval);
45
+ }
46
+ /** @inheritdoc */
47
+ async write(event) {
48
+ this.buffer.push(event);
49
+ if (this.buffer.length >= this.batchSize) await this.flush();
50
+ }
51
+ /** @inheritdoc */
52
+ async close() {
53
+ if (this.timer !== null) {
54
+ clearInterval(this.timer);
55
+ this.timer = null;
56
+ }
57
+ if (this.buffer.length > 0) await this.flush();
58
+ }
59
+ async flush() {
60
+ const batch = this.buffer;
61
+ this.buffer = [];
62
+ await this.send(batch);
63
+ }
64
+ async send(events) {
65
+ const body = JSON.stringify(this.transform ? this.transform(events) : { events });
66
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
67
+ const response = await fetch(this.url, {
68
+ method: this.method,
69
+ headers: this.headers,
70
+ body
71
+ });
72
+ if (response.ok) return;
73
+ if (!(response.status === 429 || response.status >= 500)) throw new Error(`HTTP ${String(response.status)}: ${response.statusText}`);
74
+ if (attempt < this.maxRetries) {
75
+ const delay = Math.pow(2, attempt) * 1e3 + Math.random() * 500;
76
+ await this.sleep(delay);
77
+ }
78
+ }
79
+ throw new Error(`Failed after ${String(this.maxRetries + 1)} attempts`);
80
+ }
81
+ async sleep(ms) {
82
+ return new Promise((resolve) => setTimeout(resolve, ms));
83
+ }
84
+ };
85
+
86
+ //#endregion
87
+ exports.HttpAdapter = HttpAdapter;
88
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Event, OutputAdapter } from '@synode/core';\n\n/**\n * Configuration options for {@link HttpAdapter}.\n *\n * @param url - Target endpoint URL\n * @param method - HTTP method to use. Defaults to `'POST'`.\n * @param headers - Additional headers merged with the default `Content-Type: application/json`.\n * @param batchSize - Number of events to buffer before sending. Defaults to `1`.\n * @param flushInterval - Interval in milliseconds to flush the buffer. Defaults to `5000`.\n * @param maxRetries - Maximum retry attempts for 5xx/429 responses. Defaults to `3`.\n * @param transform - Optional function to transform the event batch before serialization.\n * When omitted the body is `{ events: [...] }`.\n */\nexport interface HttpAdapterOptions {\n url: string;\n method?: 'POST' | 'PUT';\n headers?: Record<string, string>;\n batchSize?: number;\n flushInterval?: number;\n maxRetries?: number;\n transform?: (events: Event[]) => unknown;\n}\n\n/**\n * Adapter that sends events to an HTTP endpoint with batching and retry.\n *\n * Events are buffered until `batchSize` is reached, then sent as a JSON payload.\n * An optional `flushInterval` timer flushes partial batches periodically.\n * Retries with exponential backoff on 5xx and 429 responses.\n *\n * @example\n * ```ts\n * const adapter = new HttpAdapter({\n * url: 'https://api.example.com/events',\n * batchSize: 10,\n * headers: { Authorization: 'Bearer token' },\n * });\n * await generate(journey, { users: 50, adapter });\n * await adapter.close();\n * ```\n */\nexport class HttpAdapter implements OutputAdapter {\n private readonly url: string;\n private readonly method: 'POST' | 'PUT';\n private readonly headers: Record<string, string>;\n private readonly batchSize: number;\n private readonly flushInterval: number;\n private readonly maxRetries: number;\n private readonly transform?: (events: Event[]) => unknown;\n\n private buffer: Event[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: HttpAdapterOptions) {\n this.url = options.url;\n this.method = options.method ?? 'POST';\n this.headers = { 'Content-Type': 'application/json', ...options.headers };\n this.batchSize = options.batchSize ?? 1;\n this.flushInterval = options.flushInterval ?? 5000;\n this.maxRetries = options.maxRetries ?? 3;\n this.transform = options.transform;\n\n if (this.flushInterval > 0) {\n this.timer = setInterval(() => {\n if (this.buffer.length > 0) {\n void this.flush();\n }\n }, this.flushInterval);\n }\n }\n\n /** @inheritdoc */\n async write(event: Event): Promise<void> {\n this.buffer.push(event);\n if (this.buffer.length >= this.batchSize) {\n await this.flush();\n }\n }\n\n /** @inheritdoc */\n async close(): Promise<void> {\n if (this.timer !== null) {\n clearInterval(this.timer);\n this.timer = null;\n }\n if (this.buffer.length > 0) {\n await this.flush();\n }\n }\n\n private async flush(): Promise<void> {\n const batch = this.buffer;\n this.buffer = [];\n await this.send(batch);\n }\n\n private async send(events: Event[]): Promise<void> {\n const body = JSON.stringify(this.transform ? this.transform(events) : { events });\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n const response = await fetch(this.url, {\n method: this.method,\n headers: this.headers,\n body,\n });\n\n if (response.ok) {\n return;\n }\n\n const isRetryable = response.status === 429 || response.status >= 500;\n if (!isRetryable) {\n throw new Error(`HTTP ${String(response.status)}: ${response.statusText}`);\n }\n\n if (attempt < this.maxRetries) {\n const delay = Math.pow(2, attempt) * 1000 + Math.random() * 500;\n await this.sleep(delay);\n }\n }\n\n throw new Error(`Failed after ${String(this.maxRetries + 1)} attempts`);\n }\n\n private async sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,cAAb,MAAkD;CAChD,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,AAAQ,SAAkB,EAAE;CAC5B,AAAQ,QAA+C;CAEvD,YAAY,SAA6B;AACvC,OAAK,MAAM,QAAQ;AACnB,OAAK,SAAS,QAAQ,UAAU;AAChC,OAAK,UAAU;GAAE,gBAAgB;GAAoB,GAAG,QAAQ;GAAS;AACzE,OAAK,YAAY,QAAQ,aAAa;AACtC,OAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,OAAK,aAAa,QAAQ,cAAc;AACxC,OAAK,YAAY,QAAQ;AAEzB,MAAI,KAAK,gBAAgB,EACvB,MAAK,QAAQ,kBAAkB;AAC7B,OAAI,KAAK,OAAO,SAAS,EACvB,CAAK,KAAK,OAAO;KAElB,KAAK,cAAc;;;CAK1B,MAAM,MAAM,OAA6B;AACvC,OAAK,OAAO,KAAK,MAAM;AACvB,MAAI,KAAK,OAAO,UAAU,KAAK,UAC7B,OAAM,KAAK,OAAO;;;CAKtB,MAAM,QAAuB;AAC3B,MAAI,KAAK,UAAU,MAAM;AACvB,iBAAc,KAAK,MAAM;AACzB,QAAK,QAAQ;;AAEf,MAAI,KAAK,OAAO,SAAS,EACvB,OAAM,KAAK,OAAO;;CAItB,MAAc,QAAuB;EACnC,MAAM,QAAQ,KAAK;AACnB,OAAK,SAAS,EAAE;AAChB,QAAM,KAAK,KAAK,MAAM;;CAGxB,MAAc,KAAK,QAAgC;EACjD,MAAM,OAAO,KAAK,UAAU,KAAK,YAAY,KAAK,UAAU,OAAO,GAAG,EAAE,QAAQ,CAAC;AAEjF,OAAK,IAAI,UAAU,GAAG,WAAW,KAAK,YAAY,WAAW;GAC3D,MAAM,WAAW,MAAM,MAAM,KAAK,KAAK;IACrC,QAAQ,KAAK;IACb,SAAS,KAAK;IACd;IACD,CAAC;AAEF,OAAI,SAAS,GACX;AAIF,OAAI,EADgB,SAAS,WAAW,OAAO,SAAS,UAAU,KAEhE,OAAM,IAAI,MAAM,QAAQ,OAAO,SAAS,OAAO,CAAC,IAAI,SAAS,aAAa;AAG5E,OAAI,UAAU,KAAK,YAAY;IAC7B,MAAM,QAAQ,KAAK,IAAI,GAAG,QAAQ,GAAG,MAAO,KAAK,QAAQ,GAAG;AAC5D,UAAM,KAAK,MAAM,MAAM;;;AAI3B,QAAM,IAAI,MAAM,gBAAgB,OAAO,KAAK,aAAa,EAAE,CAAC,WAAW;;CAGzE,MAAc,MAAM,IAA2B;AAC7C,SAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { Event, OutputAdapter } from "@synode/core";
2
+
3
+ //#region src/index.d.ts
4
+
5
+ /**
6
+ * Configuration options for {@link HttpAdapter}.
7
+ *
8
+ * @param url - Target endpoint URL
9
+ * @param method - HTTP method to use. Defaults to `'POST'`.
10
+ * @param headers - Additional headers merged with the default `Content-Type: application/json`.
11
+ * @param batchSize - Number of events to buffer before sending. Defaults to `1`.
12
+ * @param flushInterval - Interval in milliseconds to flush the buffer. Defaults to `5000`.
13
+ * @param maxRetries - Maximum retry attempts for 5xx/429 responses. Defaults to `3`.
14
+ * @param transform - Optional function to transform the event batch before serialization.
15
+ * When omitted the body is `{ events: [...] }`.
16
+ */
17
+ interface HttpAdapterOptions {
18
+ url: string;
19
+ method?: 'POST' | 'PUT';
20
+ headers?: Record<string, string>;
21
+ batchSize?: number;
22
+ flushInterval?: number;
23
+ maxRetries?: number;
24
+ transform?: (events: Event[]) => unknown;
25
+ }
26
+ /**
27
+ * Adapter that sends events to an HTTP endpoint with batching and retry.
28
+ *
29
+ * Events are buffered until `batchSize` is reached, then sent as a JSON payload.
30
+ * An optional `flushInterval` timer flushes partial batches periodically.
31
+ * Retries with exponential backoff on 5xx and 429 responses.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const adapter = new HttpAdapter({
36
+ * url: 'https://api.example.com/events',
37
+ * batchSize: 10,
38
+ * headers: { Authorization: 'Bearer token' },
39
+ * });
40
+ * await generate(journey, { users: 50, adapter });
41
+ * await adapter.close();
42
+ * ```
43
+ */
44
+ declare class HttpAdapter implements OutputAdapter {
45
+ private readonly url;
46
+ private readonly method;
47
+ private readonly headers;
48
+ private readonly batchSize;
49
+ private readonly flushInterval;
50
+ private readonly maxRetries;
51
+ private readonly transform?;
52
+ private buffer;
53
+ private timer;
54
+ constructor(options: HttpAdapterOptions);
55
+ /** @inheritdoc */
56
+ write(event: Event): Promise<void>;
57
+ /** @inheritdoc */
58
+ close(): Promise<void>;
59
+ private flush;
60
+ private send;
61
+ private sleep;
62
+ }
63
+ //#endregion
64
+ export { HttpAdapter, HttpAdapterOptions };
65
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,65 @@
1
+ import { Event, OutputAdapter } from "@synode/core";
2
+
3
+ //#region src/index.d.ts
4
+
5
+ /**
6
+ * Configuration options for {@link HttpAdapter}.
7
+ *
8
+ * @param url - Target endpoint URL
9
+ * @param method - HTTP method to use. Defaults to `'POST'`.
10
+ * @param headers - Additional headers merged with the default `Content-Type: application/json`.
11
+ * @param batchSize - Number of events to buffer before sending. Defaults to `1`.
12
+ * @param flushInterval - Interval in milliseconds to flush the buffer. Defaults to `5000`.
13
+ * @param maxRetries - Maximum retry attempts for 5xx/429 responses. Defaults to `3`.
14
+ * @param transform - Optional function to transform the event batch before serialization.
15
+ * When omitted the body is `{ events: [...] }`.
16
+ */
17
+ interface HttpAdapterOptions {
18
+ url: string;
19
+ method?: 'POST' | 'PUT';
20
+ headers?: Record<string, string>;
21
+ batchSize?: number;
22
+ flushInterval?: number;
23
+ maxRetries?: number;
24
+ transform?: (events: Event[]) => unknown;
25
+ }
26
+ /**
27
+ * Adapter that sends events to an HTTP endpoint with batching and retry.
28
+ *
29
+ * Events are buffered until `batchSize` is reached, then sent as a JSON payload.
30
+ * An optional `flushInterval` timer flushes partial batches periodically.
31
+ * Retries with exponential backoff on 5xx and 429 responses.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const adapter = new HttpAdapter({
36
+ * url: 'https://api.example.com/events',
37
+ * batchSize: 10,
38
+ * headers: { Authorization: 'Bearer token' },
39
+ * });
40
+ * await generate(journey, { users: 50, adapter });
41
+ * await adapter.close();
42
+ * ```
43
+ */
44
+ declare class HttpAdapter implements OutputAdapter {
45
+ private readonly url;
46
+ private readonly method;
47
+ private readonly headers;
48
+ private readonly batchSize;
49
+ private readonly flushInterval;
50
+ private readonly maxRetries;
51
+ private readonly transform?;
52
+ private buffer;
53
+ private timer;
54
+ constructor(options: HttpAdapterOptions);
55
+ /** @inheritdoc */
56
+ write(event: Event): Promise<void>;
57
+ /** @inheritdoc */
58
+ close(): Promise<void>;
59
+ private flush;
60
+ private send;
61
+ private sleep;
62
+ }
63
+ //#endregion
64
+ export { HttpAdapter, HttpAdapterOptions };
65
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,87 @@
1
+ //#region src/index.ts
2
+ /**
3
+ * Adapter that sends events to an HTTP endpoint with batching and retry.
4
+ *
5
+ * Events are buffered until `batchSize` is reached, then sent as a JSON payload.
6
+ * An optional `flushInterval` timer flushes partial batches periodically.
7
+ * Retries with exponential backoff on 5xx and 429 responses.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const adapter = new HttpAdapter({
12
+ * url: 'https://api.example.com/events',
13
+ * batchSize: 10,
14
+ * headers: { Authorization: 'Bearer token' },
15
+ * });
16
+ * await generate(journey, { users: 50, adapter });
17
+ * await adapter.close();
18
+ * ```
19
+ */
20
+ var HttpAdapter = class {
21
+ url;
22
+ method;
23
+ headers;
24
+ batchSize;
25
+ flushInterval;
26
+ maxRetries;
27
+ transform;
28
+ buffer = [];
29
+ timer = null;
30
+ constructor(options) {
31
+ this.url = options.url;
32
+ this.method = options.method ?? "POST";
33
+ this.headers = {
34
+ "Content-Type": "application/json",
35
+ ...options.headers
36
+ };
37
+ this.batchSize = options.batchSize ?? 1;
38
+ this.flushInterval = options.flushInterval ?? 5e3;
39
+ this.maxRetries = options.maxRetries ?? 3;
40
+ this.transform = options.transform;
41
+ if (this.flushInterval > 0) this.timer = setInterval(() => {
42
+ if (this.buffer.length > 0) this.flush();
43
+ }, this.flushInterval);
44
+ }
45
+ /** @inheritdoc */
46
+ async write(event) {
47
+ this.buffer.push(event);
48
+ if (this.buffer.length >= this.batchSize) await this.flush();
49
+ }
50
+ /** @inheritdoc */
51
+ async close() {
52
+ if (this.timer !== null) {
53
+ clearInterval(this.timer);
54
+ this.timer = null;
55
+ }
56
+ if (this.buffer.length > 0) await this.flush();
57
+ }
58
+ async flush() {
59
+ const batch = this.buffer;
60
+ this.buffer = [];
61
+ await this.send(batch);
62
+ }
63
+ async send(events) {
64
+ const body = JSON.stringify(this.transform ? this.transform(events) : { events });
65
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
66
+ const response = await fetch(this.url, {
67
+ method: this.method,
68
+ headers: this.headers,
69
+ body
70
+ });
71
+ if (response.ok) return;
72
+ if (!(response.status === 429 || response.status >= 500)) throw new Error(`HTTP ${String(response.status)}: ${response.statusText}`);
73
+ if (attempt < this.maxRetries) {
74
+ const delay = Math.pow(2, attempt) * 1e3 + Math.random() * 500;
75
+ await this.sleep(delay);
76
+ }
77
+ }
78
+ throw new Error(`Failed after ${String(this.maxRetries + 1)} attempts`);
79
+ }
80
+ async sleep(ms) {
81
+ return new Promise((resolve) => setTimeout(resolve, ms));
82
+ }
83
+ };
84
+
85
+ //#endregion
86
+ export { HttpAdapter };
87
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Event, OutputAdapter } from '@synode/core';\n\n/**\n * Configuration options for {@link HttpAdapter}.\n *\n * @param url - Target endpoint URL\n * @param method - HTTP method to use. Defaults to `'POST'`.\n * @param headers - Additional headers merged with the default `Content-Type: application/json`.\n * @param batchSize - Number of events to buffer before sending. Defaults to `1`.\n * @param flushInterval - Interval in milliseconds to flush the buffer. Defaults to `5000`.\n * @param maxRetries - Maximum retry attempts for 5xx/429 responses. Defaults to `3`.\n * @param transform - Optional function to transform the event batch before serialization.\n * When omitted the body is `{ events: [...] }`.\n */\nexport interface HttpAdapterOptions {\n url: string;\n method?: 'POST' | 'PUT';\n headers?: Record<string, string>;\n batchSize?: number;\n flushInterval?: number;\n maxRetries?: number;\n transform?: (events: Event[]) => unknown;\n}\n\n/**\n * Adapter that sends events to an HTTP endpoint with batching and retry.\n *\n * Events are buffered until `batchSize` is reached, then sent as a JSON payload.\n * An optional `flushInterval` timer flushes partial batches periodically.\n * Retries with exponential backoff on 5xx and 429 responses.\n *\n * @example\n * ```ts\n * const adapter = new HttpAdapter({\n * url: 'https://api.example.com/events',\n * batchSize: 10,\n * headers: { Authorization: 'Bearer token' },\n * });\n * await generate(journey, { users: 50, adapter });\n * await adapter.close();\n * ```\n */\nexport class HttpAdapter implements OutputAdapter {\n private readonly url: string;\n private readonly method: 'POST' | 'PUT';\n private readonly headers: Record<string, string>;\n private readonly batchSize: number;\n private readonly flushInterval: number;\n private readonly maxRetries: number;\n private readonly transform?: (events: Event[]) => unknown;\n\n private buffer: Event[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: HttpAdapterOptions) {\n this.url = options.url;\n this.method = options.method ?? 'POST';\n this.headers = { 'Content-Type': 'application/json', ...options.headers };\n this.batchSize = options.batchSize ?? 1;\n this.flushInterval = options.flushInterval ?? 5000;\n this.maxRetries = options.maxRetries ?? 3;\n this.transform = options.transform;\n\n if (this.flushInterval > 0) {\n this.timer = setInterval(() => {\n if (this.buffer.length > 0) {\n void this.flush();\n }\n }, this.flushInterval);\n }\n }\n\n /** @inheritdoc */\n async write(event: Event): Promise<void> {\n this.buffer.push(event);\n if (this.buffer.length >= this.batchSize) {\n await this.flush();\n }\n }\n\n /** @inheritdoc */\n async close(): Promise<void> {\n if (this.timer !== null) {\n clearInterval(this.timer);\n this.timer = null;\n }\n if (this.buffer.length > 0) {\n await this.flush();\n }\n }\n\n private async flush(): Promise<void> {\n const batch = this.buffer;\n this.buffer = [];\n await this.send(batch);\n }\n\n private async send(events: Event[]): Promise<void> {\n const body = JSON.stringify(this.transform ? this.transform(events) : { events });\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n const response = await fetch(this.url, {\n method: this.method,\n headers: this.headers,\n body,\n });\n\n if (response.ok) {\n return;\n }\n\n const isRetryable = response.status === 429 || response.status >= 500;\n if (!isRetryable) {\n throw new Error(`HTTP ${String(response.status)}: ${response.statusText}`);\n }\n\n if (attempt < this.maxRetries) {\n const delay = Math.pow(2, attempt) * 1000 + Math.random() * 500;\n await this.sleep(delay);\n }\n }\n\n throw new Error(`Failed after ${String(this.maxRetries + 1)} attempts`);\n }\n\n private async sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA0CA,IAAa,cAAb,MAAkD;CAChD,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,AAAQ,SAAkB,EAAE;CAC5B,AAAQ,QAA+C;CAEvD,YAAY,SAA6B;AACvC,OAAK,MAAM,QAAQ;AACnB,OAAK,SAAS,QAAQ,UAAU;AAChC,OAAK,UAAU;GAAE,gBAAgB;GAAoB,GAAG,QAAQ;GAAS;AACzE,OAAK,YAAY,QAAQ,aAAa;AACtC,OAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,OAAK,aAAa,QAAQ,cAAc;AACxC,OAAK,YAAY,QAAQ;AAEzB,MAAI,KAAK,gBAAgB,EACvB,MAAK,QAAQ,kBAAkB;AAC7B,OAAI,KAAK,OAAO,SAAS,EACvB,CAAK,KAAK,OAAO;KAElB,KAAK,cAAc;;;CAK1B,MAAM,MAAM,OAA6B;AACvC,OAAK,OAAO,KAAK,MAAM;AACvB,MAAI,KAAK,OAAO,UAAU,KAAK,UAC7B,OAAM,KAAK,OAAO;;;CAKtB,MAAM,QAAuB;AAC3B,MAAI,KAAK,UAAU,MAAM;AACvB,iBAAc,KAAK,MAAM;AACzB,QAAK,QAAQ;;AAEf,MAAI,KAAK,OAAO,SAAS,EACvB,OAAM,KAAK,OAAO;;CAItB,MAAc,QAAuB;EACnC,MAAM,QAAQ,KAAK;AACnB,OAAK,SAAS,EAAE;AAChB,QAAM,KAAK,KAAK,MAAM;;CAGxB,MAAc,KAAK,QAAgC;EACjD,MAAM,OAAO,KAAK,UAAU,KAAK,YAAY,KAAK,UAAU,OAAO,GAAG,EAAE,QAAQ,CAAC;AAEjF,OAAK,IAAI,UAAU,GAAG,WAAW,KAAK,YAAY,WAAW;GAC3D,MAAM,WAAW,MAAM,MAAM,KAAK,KAAK;IACrC,QAAQ,KAAK;IACb,SAAS,KAAK;IACd;IACD,CAAC;AAEF,OAAI,SAAS,GACX;AAIF,OAAI,EADgB,SAAS,WAAW,OAAO,SAAS,UAAU,KAEhE,OAAM,IAAI,MAAM,QAAQ,OAAO,SAAS,OAAO,CAAC,IAAI,SAAS,aAAa;AAG5E,OAAI,UAAU,KAAK,YAAY;IAC7B,MAAM,QAAQ,KAAK,IAAI,GAAG,QAAQ,GAAG,MAAO,KAAK,QAAQ,GAAG;AAC5D,UAAM,KAAK,MAAM,MAAM;;;AAI3B,QAAM,IAAI,MAAM,gBAAgB,OAAO,KAAK,aAAa,EAAE,CAAC,WAAW;;CAGzE,MAAc,MAAM,IAA2B;AAC7C,SAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@synode/adapter-http",
3
+ "version": "1.0.0",
4
+ "description": "HTTP adapter for Synode",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.mts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "author": "Digitl Cloud GmbH",
20
+ "license": "SEE LICENSE IN LICENSE",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/digitl-cloud/synode",
24
+ "directory": "packages/adapter-http"
25
+ },
26
+ "peerDependencies": {
27
+ "@synode/core": "^1.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^24.12.0",
31
+ "eslint": "^9.39.4",
32
+ "eslint-config-prettier": "^10.1.8",
33
+ "typescript-eslint": "^8.58.0",
34
+ "tsdown": "^0.16.8",
35
+ "typescript": "^5.9.3",
36
+ "vitest": "^4.1.2",
37
+ "@synode/core": "1.0.0"
38
+ },
39
+ "scripts": {
40
+ "build": "tsdown",
41
+ "test": "vitest run",
42
+ "lint": "eslint src tests"
43
+ }
44
+ }