bridgex 1.0.0 → 2.0.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/README.md +525 -0
- package/dist/CircuitBreaker.d.ts +25 -0
- package/dist/CircuitBreaker.js +58 -0
- package/dist/CredentialProvisioner.d.ts +27 -0
- package/dist/CredentialProvisioner.js +43 -0
- package/dist/MessageFormatter.d.ts +13 -1
- package/dist/MessageFormatter.js +30 -6
- package/dist/PluginManager.d.ts +77 -0
- package/dist/PluginManager.js +101 -0
- package/dist/RetryHandler.d.ts +8 -1
- package/dist/RetryHandler.js +28 -6
- package/dist/SMSClient.d.ts +65 -0
- package/dist/SMSClient.js +236 -0
- package/dist/SMSQueue.d.ts +82 -0
- package/dist/SMSQueue.js +207 -0
- package/dist/SMSScheduler.d.ts +75 -0
- package/dist/SMSScheduler.js +196 -0
- package/dist/SMSService.d.ts +41 -0
- package/dist/SMSService.js +214 -0
- package/dist/SendConfig.d.ts +90 -0
- package/dist/SendConfig.js +165 -0
- package/dist/errors.d.ts +18 -1
- package/dist/errors.js +28 -5
- package/dist/helpers.d.ts +3 -0
- package/dist/helpers.js +23 -0
- package/dist/main.d.ts +20 -1
- package/dist/main.js +19 -1
- package/dist/types.d.ts +43 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/src/CircuitBreaker.ts +81 -0
- package/src/CredentialProvisioner.ts +68 -0
- package/src/MessageFormatter.ts +38 -7
- package/src/PluginManager.ts +155 -0
- package/src/RetryHandler.ts +42 -9
- package/src/SMSClient.ts +308 -0
- package/src/SMSQueue.ts +281 -0
- package/src/SMSScheduler.ts +250 -0
- package/src/SMSService.ts +254 -0
- package/src/SendConfig.ts +208 -0
- package/src/errors.ts +40 -6
- package/src/helpers.ts +31 -0
- package/src/main.ts +61 -1
- package/src/types.ts +33 -0
- package/src/client/SMSManager.ts +0 -67
- package/src/client/types.ts +0 -24
- package/src/help.ts +0 -3
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMSService — run the SDK as a standalone HTTP microservice.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the full SMS API over REST so any other service (Python, Go, Ruby, …)
|
|
5
|
+
* can send messages without bundling the SDK itself.
|
|
6
|
+
*
|
|
7
|
+
* Built on Node's built-in http module — zero extra dependencies.
|
|
8
|
+
*
|
|
9
|
+
* Routes
|
|
10
|
+
* ──────
|
|
11
|
+
* POST /send — send a single message
|
|
12
|
+
* POST /send/many — send to many recipients
|
|
13
|
+
* POST /send/object — send from a plain object
|
|
14
|
+
* POST /send/object/many — send objects to many recipients
|
|
15
|
+
* POST /queue/enqueue — fire-and-forget via queue
|
|
16
|
+
* POST /queue/schedule — schedule a future send
|
|
17
|
+
* GET /queue/stats — queue stats
|
|
18
|
+
* DELETE /queue/:id — cancel a queued job
|
|
19
|
+
* GET /health — liveness check
|
|
20
|
+
* GET /metrics — metrics snapshot (if MetricsPlugin attached)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const service = new SMSService(client, { port: 4000, apiKey: "secret" });
|
|
24
|
+
* service.withQueue(queue).withMetrics(metricsPlugin);
|
|
25
|
+
* await service.start();
|
|
26
|
+
*
|
|
27
|
+
* // From any other service:
|
|
28
|
+
* // POST http://localhost:4000/send
|
|
29
|
+
* // Headers: x-service-key: secret
|
|
30
|
+
* // Body: { "to": "+1555…", "template": "Hello {name}", "variables": { "name": "Alice" } }
|
|
31
|
+
*/
|
|
32
|
+
import { createServer, } from "node:http";
|
|
33
|
+
export default class SMSService {
|
|
34
|
+
constructor(client, options = {}) {
|
|
35
|
+
this.client = client;
|
|
36
|
+
this.options = options;
|
|
37
|
+
this.server = createServer((req, res) => this._handle(req, res));
|
|
38
|
+
this.startedAt = new Date();
|
|
39
|
+
}
|
|
40
|
+
/** Attach a queue so /queue/* endpoints are available. */
|
|
41
|
+
withQueue(queue) {
|
|
42
|
+
this.queue = queue;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
/** Attach a MetricsPlugin to expose /metrics. */
|
|
46
|
+
withMetrics(plugin) {
|
|
47
|
+
this.metricsSnapshot = () => plugin.snapshot();
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
start() {
|
|
51
|
+
const { port = 3001, host = "0.0.0.0" } = this.options;
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
this.server.listen(port, host, () => {
|
|
54
|
+
console.log(`[SMSService] Listening on http://${host}:${port}`);
|
|
55
|
+
resolve();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
stop() {
|
|
60
|
+
return new Promise((resolve, reject) => this.server.close((err) => (err ? reject(err) : resolve())));
|
|
61
|
+
}
|
|
62
|
+
// ── Request handler ──────────────────────────────────────────────────────
|
|
63
|
+
async _handle(req, res) {
|
|
64
|
+
const { apiKey, timeout = 10000 } = this.options;
|
|
65
|
+
// Auth
|
|
66
|
+
if (apiKey && req.headers["x-service-key"] !== apiKey) {
|
|
67
|
+
return this._json(res, 401, { error: "Unauthorized" });
|
|
68
|
+
}
|
|
69
|
+
// Timeout guard
|
|
70
|
+
const timer = setTimeout(() => {
|
|
71
|
+
res.writeHead(504);
|
|
72
|
+
res.end(JSON.stringify({ error: "Gateway timeout" }));
|
|
73
|
+
}, timeout);
|
|
74
|
+
try {
|
|
75
|
+
const url = req.url?.split("?")[0] ?? "/";
|
|
76
|
+
const method = req.method ?? "GET";
|
|
77
|
+
// ── GET routes ──
|
|
78
|
+
if (method === "GET") {
|
|
79
|
+
if (url === "/health")
|
|
80
|
+
return this._health(res);
|
|
81
|
+
if (url === "/metrics")
|
|
82
|
+
return this._metrics(res);
|
|
83
|
+
if (url === "/queue/stats")
|
|
84
|
+
return this._queueStats(res);
|
|
85
|
+
return this._json(res, 404, { error: "Not found" });
|
|
86
|
+
}
|
|
87
|
+
// ── DELETE routes ──
|
|
88
|
+
if (method === "DELETE") {
|
|
89
|
+
const match = url.match(/^\/queue\/(.+)$/);
|
|
90
|
+
if (match)
|
|
91
|
+
return this._queueCancel(res, match[1]);
|
|
92
|
+
return this._json(res, 404, { error: "Not found" });
|
|
93
|
+
}
|
|
94
|
+
// ── POST routes ──
|
|
95
|
+
if (method === "POST") {
|
|
96
|
+
const body = await this._readBody(req);
|
|
97
|
+
if (url === "/send")
|
|
98
|
+
return this._send(res, body);
|
|
99
|
+
if (url === "/send/many")
|
|
100
|
+
return this._sendMany(res, body);
|
|
101
|
+
if (url === "/send/object")
|
|
102
|
+
return this._sendObject(res, body);
|
|
103
|
+
if (url === "/send/object/many")
|
|
104
|
+
return this._sendObjectMany(res, body);
|
|
105
|
+
if (url === "/queue/enqueue")
|
|
106
|
+
return this._enqueue(res, body);
|
|
107
|
+
if (url === "/queue/schedule")
|
|
108
|
+
return this._schedule(res, body);
|
|
109
|
+
return this._json(res, 404, { error: "Not found" });
|
|
110
|
+
}
|
|
111
|
+
this._json(res, 405, { error: "Method not allowed" });
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
this._json(res, 500, { error: err.message ?? "Internal error" });
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ── Route handlers ────────────────────────────────────────────────────────
|
|
121
|
+
async _send(res, body) {
|
|
122
|
+
const result = await this.client.send(body);
|
|
123
|
+
this._json(res, result.ok ? 200 : 422, result);
|
|
124
|
+
}
|
|
125
|
+
async _sendMany(res, body) {
|
|
126
|
+
const { recipients, template, sharedVariables } = body;
|
|
127
|
+
const result = await this.client.sendMany(recipients, template, sharedVariables);
|
|
128
|
+
this._json(res, 200, result);
|
|
129
|
+
}
|
|
130
|
+
async _sendObject(res, body) {
|
|
131
|
+
const result = await this.client.sendObject(body);
|
|
132
|
+
this._json(res, result.ok ? 200 : 422, result);
|
|
133
|
+
}
|
|
134
|
+
async _sendObjectMany(res, body) {
|
|
135
|
+
const result = await this.client.sendObjectMany(body.items ?? body);
|
|
136
|
+
this._json(res, 200, result);
|
|
137
|
+
}
|
|
138
|
+
_enqueue(res, body) {
|
|
139
|
+
if (!this.queue)
|
|
140
|
+
return this._json(res, 503, { error: "Queue not configured" });
|
|
141
|
+
const id = this.queue.enqueue(body);
|
|
142
|
+
this._json(res, 202, { id, status: "queued" });
|
|
143
|
+
}
|
|
144
|
+
_schedule(res, body) {
|
|
145
|
+
if (!this.queue)
|
|
146
|
+
return this._json(res, 503, { error: "Queue not configured" });
|
|
147
|
+
const { at, delayMs, ...params } = body;
|
|
148
|
+
let id;
|
|
149
|
+
if (at) {
|
|
150
|
+
id = this.queue.enqueueAt(params, new Date(at).getTime());
|
|
151
|
+
}
|
|
152
|
+
else if (delayMs) {
|
|
153
|
+
id = this.queue.enqueueAfter(params, delayMs);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
return this._json(res, 400, {
|
|
157
|
+
error: "Provide 'at' (ISO date) or 'delayMs'",
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
this._json(res, 202, { id, status: "scheduled" });
|
|
161
|
+
}
|
|
162
|
+
_queueStats(res) {
|
|
163
|
+
if (!this.queue)
|
|
164
|
+
return this._json(res, 503, { error: "Queue not configured" });
|
|
165
|
+
this._json(res, 200, this.queue.stats());
|
|
166
|
+
}
|
|
167
|
+
_queueCancel(res, id) {
|
|
168
|
+
if (!this.queue)
|
|
169
|
+
return this._json(res, 503, { error: "Queue not configured" });
|
|
170
|
+
const ok = this.queue.cancel(id);
|
|
171
|
+
this._json(res, ok ? 200 : 404, { id, cancelled: ok });
|
|
172
|
+
}
|
|
173
|
+
_health(res) {
|
|
174
|
+
this._json(res, 200, {
|
|
175
|
+
status: "ok",
|
|
176
|
+
circuitState: this.client.circuitState,
|
|
177
|
+
uptime: Math.floor((Date.now() - this.startedAt.getTime()) / 1000),
|
|
178
|
+
startedAt: this.startedAt.toISOString(),
|
|
179
|
+
queue: this.queue?.stats() ?? null,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
_metrics(res) {
|
|
183
|
+
if (!this.metricsSnapshot) {
|
|
184
|
+
return this._json(res, 503, {
|
|
185
|
+
error: "Metrics plugin not attached. Call .withMetrics()",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
this._json(res, 200, this.metricsSnapshot());
|
|
189
|
+
}
|
|
190
|
+
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
191
|
+
_json(res, status, body) {
|
|
192
|
+
const payload = JSON.stringify(body);
|
|
193
|
+
res.writeHead(status, {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
196
|
+
});
|
|
197
|
+
res.end(payload);
|
|
198
|
+
}
|
|
199
|
+
_readBody(req) {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const chunks = [];
|
|
202
|
+
req.on("data", (c) => chunks.push(c));
|
|
203
|
+
req.on("end", () => {
|
|
204
|
+
try {
|
|
205
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString() || "{}"));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
reject(new Error("Invalid JSON body"));
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
req.on("error", reject);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { SendParams, SendObjectParams } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* SendConfig — fluent builder for reusable send settings.
|
|
4
|
+
*
|
|
5
|
+
* Build once, reuse everywhere. Eliminates repeating template/variables
|
|
6
|
+
* across multiple send calls. Supports overrides per-call.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const otpConfig = new SendConfig()
|
|
10
|
+
* .template("Your verification code is {code}. Expires in {expiryMinutes} minutes.")
|
|
11
|
+
* .defaults({ expiryMinutes: 10 })
|
|
12
|
+
* .tag("otp");
|
|
13
|
+
*
|
|
14
|
+
* await client.send(otpConfig.for("+15551234567", { code: "482910" }));
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Object config with auto-extraction
|
|
18
|
+
* const orderConfig = new SendConfig()
|
|
19
|
+
* .template("Hi {customer.name}, your order {orderId} is {status}.")
|
|
20
|
+
* .tag("order-notification");
|
|
21
|
+
*
|
|
22
|
+
* await client.sendObject(orderConfig.forObject("+15551234567", order));
|
|
23
|
+
*/
|
|
24
|
+
export default class SendConfig {
|
|
25
|
+
private _template?;
|
|
26
|
+
private _defaults;
|
|
27
|
+
private _tags;
|
|
28
|
+
private _ttl?;
|
|
29
|
+
private _priority;
|
|
30
|
+
private _dedupKey?;
|
|
31
|
+
/** Set the message template. Supports {variable} and {nested.path} placeholders. */
|
|
32
|
+
template(tpl: string): this;
|
|
33
|
+
/** Default variable values merged under per-call variables. */
|
|
34
|
+
defaults(vars: Record<string, unknown>): this;
|
|
35
|
+
/** Add a single default variable. */
|
|
36
|
+
set(key: string, value: unknown): this;
|
|
37
|
+
/** Tag this config (useful for logging, filtering, hooks). */
|
|
38
|
+
tag(...tags: string[]): this;
|
|
39
|
+
/**
|
|
40
|
+
* Time-to-live in milliseconds.
|
|
41
|
+
* Messages scheduled/queued past this age will be dropped.
|
|
42
|
+
*/
|
|
43
|
+
ttl(ms: number): this;
|
|
44
|
+
/** Queue priority. High-priority jobs jump the queue. */
|
|
45
|
+
priority(p: "low" | "normal" | "high"): this;
|
|
46
|
+
/**
|
|
47
|
+
* Deduplication key.
|
|
48
|
+
* If two messages with the same dedupKey are enqueued within the TTL window,
|
|
49
|
+
* the second is silently dropped.
|
|
50
|
+
*/
|
|
51
|
+
dedupKey(key: string): this;
|
|
52
|
+
/** Build a SendParams for a single recipient. Variables override defaults. */
|
|
53
|
+
for(to: string, variables?: Record<string, unknown>): SendParams & SendMeta;
|
|
54
|
+
/** Build a SendObjectParams for a single recipient. */
|
|
55
|
+
forObject<T extends Record<string, unknown>>(to: string, object: T): SendObjectParams<T> & SendMeta;
|
|
56
|
+
/**
|
|
57
|
+
* Build SendParams for many recipients from an array.
|
|
58
|
+
* Each recipient entry can supply per-recipient variables.
|
|
59
|
+
*/
|
|
60
|
+
forMany(recipients: Array<{
|
|
61
|
+
to: string;
|
|
62
|
+
variables?: Record<string, unknown>;
|
|
63
|
+
}>): Array<SendParams & SendMeta>;
|
|
64
|
+
/** Produce the metadata snapshot for queuing / logging. */
|
|
65
|
+
meta(): SendMeta["_meta"];
|
|
66
|
+
/** Clone this config so you can branch without mutating the original. */
|
|
67
|
+
clone(): SendConfig;
|
|
68
|
+
/**
|
|
69
|
+
* Merge another config on top of this one (other wins on conflicts).
|
|
70
|
+
* Returns a new config — neither is mutated.
|
|
71
|
+
*/
|
|
72
|
+
merge(other: SendConfig): SendConfig;
|
|
73
|
+
/** Create an OTP config. */
|
|
74
|
+
static otp(expiryMinutes?: number): SendConfig;
|
|
75
|
+
/** Create an order-notification config. */
|
|
76
|
+
static orderNotification(): SendConfig;
|
|
77
|
+
/** Create a reminder config. */
|
|
78
|
+
static reminder(): SendConfig;
|
|
79
|
+
/** Create a marketing/promo config. */
|
|
80
|
+
static promo(): SendConfig;
|
|
81
|
+
}
|
|
82
|
+
export interface SendMeta {
|
|
83
|
+
_meta: {
|
|
84
|
+
tags: string[];
|
|
85
|
+
ttl?: number;
|
|
86
|
+
priority: "low" | "normal" | "high";
|
|
87
|
+
dedupKey?: string;
|
|
88
|
+
createdAt: number;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SendConfig — fluent builder for reusable send settings.
|
|
3
|
+
*
|
|
4
|
+
* Build once, reuse everywhere. Eliminates repeating template/variables
|
|
5
|
+
* across multiple send calls. Supports overrides per-call.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const otpConfig = new SendConfig()
|
|
9
|
+
* .template("Your verification code is {code}. Expires in {expiryMinutes} minutes.")
|
|
10
|
+
* .defaults({ expiryMinutes: 10 })
|
|
11
|
+
* .tag("otp");
|
|
12
|
+
*
|
|
13
|
+
* await client.send(otpConfig.for("+15551234567", { code: "482910" }));
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // Object config with auto-extraction
|
|
17
|
+
* const orderConfig = new SendConfig()
|
|
18
|
+
* .template("Hi {customer.name}, your order {orderId} is {status}.")
|
|
19
|
+
* .tag("order-notification");
|
|
20
|
+
*
|
|
21
|
+
* await client.sendObject(orderConfig.forObject("+15551234567", order));
|
|
22
|
+
*/
|
|
23
|
+
export default class SendConfig {
|
|
24
|
+
constructor() {
|
|
25
|
+
this._defaults = {};
|
|
26
|
+
this._tags = [];
|
|
27
|
+
this._priority = "normal";
|
|
28
|
+
}
|
|
29
|
+
// ── Builder methods ──────────────────────────────────────────────────────
|
|
30
|
+
/** Set the message template. Supports {variable} and {nested.path} placeholders. */
|
|
31
|
+
template(tpl) {
|
|
32
|
+
this._template = tpl;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/** Default variable values merged under per-call variables. */
|
|
36
|
+
defaults(vars) {
|
|
37
|
+
this._defaults = { ...this._defaults, ...vars };
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
/** Add a single default variable. */
|
|
41
|
+
set(key, value) {
|
|
42
|
+
this._defaults[key] = value;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
/** Tag this config (useful for logging, filtering, hooks). */
|
|
46
|
+
tag(...tags) {
|
|
47
|
+
this._tags.push(...tags);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Time-to-live in milliseconds.
|
|
52
|
+
* Messages scheduled/queued past this age will be dropped.
|
|
53
|
+
*/
|
|
54
|
+
ttl(ms) {
|
|
55
|
+
this._ttl = ms;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/** Queue priority. High-priority jobs jump the queue. */
|
|
59
|
+
priority(p) {
|
|
60
|
+
this._priority = p;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Deduplication key.
|
|
65
|
+
* If two messages with the same dedupKey are enqueued within the TTL window,
|
|
66
|
+
* the second is silently dropped.
|
|
67
|
+
*/
|
|
68
|
+
dedupKey(key) {
|
|
69
|
+
this._dedupKey = key;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
// ── Materialise ──────────────────────────────────────────────────────────
|
|
73
|
+
/** Build a SendParams for a single recipient. Variables override defaults. */
|
|
74
|
+
for(to, variables) {
|
|
75
|
+
if (!this._template) {
|
|
76
|
+
throw new Error("SendConfig: template is required. Call .template() first.");
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
to,
|
|
80
|
+
template: this._template,
|
|
81
|
+
variables: { ...this._defaults, ...variables },
|
|
82
|
+
_meta: this.meta(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Build a SendObjectParams for a single recipient. */
|
|
86
|
+
forObject(to, object) {
|
|
87
|
+
return {
|
|
88
|
+
to,
|
|
89
|
+
object,
|
|
90
|
+
template: this._template,
|
|
91
|
+
_meta: this.meta(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build SendParams for many recipients from an array.
|
|
96
|
+
* Each recipient entry can supply per-recipient variables.
|
|
97
|
+
*/
|
|
98
|
+
forMany(recipients) {
|
|
99
|
+
return recipients.map((r) => this.for(r.to, r.variables));
|
|
100
|
+
}
|
|
101
|
+
/** Produce the metadata snapshot for queuing / logging. */
|
|
102
|
+
meta() {
|
|
103
|
+
return {
|
|
104
|
+
tags: [...this._tags],
|
|
105
|
+
ttl: this._ttl,
|
|
106
|
+
priority: this._priority,
|
|
107
|
+
dedupKey: this._dedupKey,
|
|
108
|
+
createdAt: Date.now(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// ── Presets ──────────────────────────────────────────────────────────────
|
|
112
|
+
/** Clone this config so you can branch without mutating the original. */
|
|
113
|
+
clone() {
|
|
114
|
+
const c = new SendConfig();
|
|
115
|
+
c._template = this._template;
|
|
116
|
+
c._defaults = { ...this._defaults };
|
|
117
|
+
c._tags = [...this._tags];
|
|
118
|
+
c._ttl = this._ttl;
|
|
119
|
+
c._priority = this._priority;
|
|
120
|
+
c._dedupKey = this._dedupKey;
|
|
121
|
+
return c;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Merge another config on top of this one (other wins on conflicts).
|
|
125
|
+
* Returns a new config — neither is mutated.
|
|
126
|
+
*/
|
|
127
|
+
merge(other) {
|
|
128
|
+
return this.clone()
|
|
129
|
+
.template(other._template ?? this._template ?? "")
|
|
130
|
+
.defaults({ ...this._defaults, ...other._defaults })
|
|
131
|
+
.tag(...other._tags)
|
|
132
|
+
.priority(other._priority);
|
|
133
|
+
}
|
|
134
|
+
// ── Static factories ─────────────────────────────────────────────────────
|
|
135
|
+
/** Create an OTP config. */
|
|
136
|
+
static otp(expiryMinutes = 10) {
|
|
137
|
+
return new SendConfig()
|
|
138
|
+
.template("Your verification code is {code}. Expires in {expiryMinutes} minutes.")
|
|
139
|
+
.defaults({ expiryMinutes })
|
|
140
|
+
.tag("otp")
|
|
141
|
+
.ttl(expiryMinutes * 60 * 1000)
|
|
142
|
+
.priority("high");
|
|
143
|
+
}
|
|
144
|
+
/** Create an order-notification config. */
|
|
145
|
+
static orderNotification() {
|
|
146
|
+
return new SendConfig()
|
|
147
|
+
.template("Hi {name}, your order #{orderId} is now {status}.")
|
|
148
|
+
.tag("order")
|
|
149
|
+
.priority("normal");
|
|
150
|
+
}
|
|
151
|
+
/** Create a reminder config. */
|
|
152
|
+
static reminder() {
|
|
153
|
+
return new SendConfig()
|
|
154
|
+
.template("Reminder: {message}")
|
|
155
|
+
.tag("reminder")
|
|
156
|
+
.priority("low");
|
|
157
|
+
}
|
|
158
|
+
/** Create a marketing/promo config. */
|
|
159
|
+
static promo() {
|
|
160
|
+
return new SendConfig()
|
|
161
|
+
.template("{promoMessage}")
|
|
162
|
+
.tag("promo", "marketing")
|
|
163
|
+
.priority("low");
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
declare class SMSClientError extends Error {
|
|
2
2
|
code: string;
|
|
3
3
|
details?: unknown;
|
|
4
|
-
|
|
4
|
+
isClientError: boolean;
|
|
5
|
+
isServerError: boolean;
|
|
6
|
+
constructor(message: string, code: string, details?: unknown, isClientError?: boolean, isServerError?: boolean);
|
|
7
|
+
toJSON(): {
|
|
8
|
+
name: string;
|
|
9
|
+
message: string;
|
|
10
|
+
code: string;
|
|
11
|
+
details: unknown;
|
|
12
|
+
isClientError: boolean;
|
|
13
|
+
isServerError: boolean;
|
|
14
|
+
};
|
|
5
15
|
}
|
|
6
16
|
export declare class ServerError extends SMSClientError {
|
|
7
17
|
constructor(message: string, details?: unknown);
|
|
@@ -15,4 +25,11 @@ export declare class TemplateError extends SMSClientError {
|
|
|
15
25
|
export declare class NetworkError extends SMSClientError {
|
|
16
26
|
constructor(message: string, details?: unknown);
|
|
17
27
|
}
|
|
28
|
+
export declare class RateLimitError extends SMSClientError {
|
|
29
|
+
retryAfter?: number;
|
|
30
|
+
constructor(message: string, retryAfter?: number, details?: unknown);
|
|
31
|
+
}
|
|
32
|
+
export declare class CircuitOpenError extends SMSClientError {
|
|
33
|
+
constructor(message: string, details?: unknown);
|
|
34
|
+
}
|
|
18
35
|
export {};
|
package/dist/errors.js
CHANGED
|
@@ -1,29 +1,52 @@
|
|
|
1
1
|
class SMSClientError extends Error {
|
|
2
|
-
constructor(message, code, details) {
|
|
2
|
+
constructor(message, code, details, isClientError = false, isServerError = false) {
|
|
3
3
|
super(message);
|
|
4
4
|
this.name = this.constructor.name;
|
|
5
5
|
this.code = code;
|
|
6
6
|
this.details = details;
|
|
7
|
+
this.isClientError = isClientError;
|
|
8
|
+
this.isServerError = isServerError;
|
|
7
9
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
8
10
|
}
|
|
11
|
+
toJSON() {
|
|
12
|
+
return {
|
|
13
|
+
name: this.name,
|
|
14
|
+
message: this.message,
|
|
15
|
+
code: this.code,
|
|
16
|
+
details: this.details,
|
|
17
|
+
isClientError: this.isClientError,
|
|
18
|
+
isServerError: this.isServerError,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
9
21
|
}
|
|
10
22
|
export class ServerError extends SMSClientError {
|
|
11
23
|
constructor(message, details) {
|
|
12
|
-
super(message, "SERVER_ERROR", details);
|
|
24
|
+
super(message, "SERVER_ERROR", details, false, true);
|
|
13
25
|
}
|
|
14
26
|
}
|
|
15
27
|
export class ValidationError extends SMSClientError {
|
|
16
28
|
constructor(message, details) {
|
|
17
|
-
super(message, "VALIDATION_ERROR", details);
|
|
29
|
+
super(message, "VALIDATION_ERROR", details, true, false);
|
|
18
30
|
}
|
|
19
31
|
}
|
|
20
32
|
export class TemplateError extends SMSClientError {
|
|
21
33
|
constructor(message, details) {
|
|
22
|
-
super(message, "TEMPLATE_ERROR", details);
|
|
34
|
+
super(message, "TEMPLATE_ERROR", details, true, false);
|
|
23
35
|
}
|
|
24
36
|
}
|
|
25
37
|
export class NetworkError extends SMSClientError {
|
|
26
38
|
constructor(message, details) {
|
|
27
|
-
super(message, "NETWORK_ERROR", details);
|
|
39
|
+
super(message, "NETWORK_ERROR", details, false, false);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export class RateLimitError extends SMSClientError {
|
|
43
|
+
constructor(message, retryAfter, details) {
|
|
44
|
+
super(message, "RATE_LIMIT_ERROR", details, false, true);
|
|
45
|
+
this.retryAfter = retryAfter;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class CircuitOpenError extends SMSClientError {
|
|
49
|
+
constructor(message, details) {
|
|
50
|
+
super(message, "CIRCUIT_OPEN", details, false, false);
|
|
28
51
|
}
|
|
29
52
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function isObject(value: unknown): value is Record<string, unknown>;
|
|
2
|
+
export declare function flattenObject(obj: Record<string, unknown>, prefix?: string, separator?: string): Record<string, string>;
|
|
3
|
+
export declare function chunkArray<T>(array: T[], size: number): T[][];
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function isObject(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function flattenObject(obj, prefix = "", separator = ".") {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
7
|
+
const fullKey = prefix ? `${prefix}${separator}${key}` : key;
|
|
8
|
+
if (isObject(value)) {
|
|
9
|
+
Object.assign(result, flattenObject(value, fullKey, separator));
|
|
10
|
+
}
|
|
11
|
+
else if (value !== null && value !== undefined) {
|
|
12
|
+
result[fullKey] = String(value);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
export function chunkArray(array, size) {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
for (let i = 0; i < array.length; i += size) {
|
|
20
|
+
chunks.push(array.slice(i, i + size));
|
|
21
|
+
}
|
|
22
|
+
return chunks;
|
|
23
|
+
}
|
package/dist/main.d.ts
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
|
-
export { SMSClient } from "./
|
|
1
|
+
export { default as SMSClient } from "./SMSClient.js";
|
|
2
|
+
export type { SMSClientOptions } from "./SMSClient.js";
|
|
3
|
+
export { default as SendConfig } from "./SendConfig.js";
|
|
4
|
+
export type { SendMeta } from "./SendConfig.js";
|
|
5
|
+
export { default as SMSService } from "./SMSService.js";
|
|
6
|
+
export type { SMSServiceOptions } from "./SMSService.js";
|
|
7
|
+
export { default as SMSQueue } from "./SMSQueue.js";
|
|
8
|
+
export type { QueueOptions, QueueStats, QueueJob } from "./SMSQueue.js";
|
|
9
|
+
export { default as SMSScheduler } from "./SMSScheduler.js";
|
|
10
|
+
export type { ScheduledJob } from "./SMSScheduler.js";
|
|
11
|
+
export { default as PluginManager, LoggerPlugin, MetricsPlugin, } from "./PluginManager.js";
|
|
12
|
+
export type { Plugin, PluginHooks, MetricsSnapshot } from "./PluginManager.js";
|
|
13
|
+
export { default as CredentialProvisioner, type ProvisionOptions, type Credentials, } from "./CredentialProvisioner.js";
|
|
14
|
+
export { default as CircuitBreaker } from "./CircuitBreaker.js";
|
|
15
|
+
export type { CircuitBreakerOptions } from "./CircuitBreaker.js";
|
|
16
|
+
export { default as RetryHandler } from "./RetryHandler.js";
|
|
17
|
+
export type { RetryOptions } from "./RetryHandler.js";
|
|
18
|
+
export { default as MessageFormatter } from "./MessageFormatter.js";
|
|
19
|
+
export { ServerError, ValidationError, TemplateError, NetworkError, RateLimitError, CircuitOpenError, } from "./errors.js";
|
|
20
|
+
export type { Result, ErrorLog, SendParams, SendObjectParams, BatchResult, } from "./types.js";
|
package/dist/main.js
CHANGED
|
@@ -1 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
// Core client
|
|
2
|
+
export { default as SMSClient } from "./SMSClient.js";
|
|
3
|
+
// Config builder
|
|
4
|
+
export { default as SendConfig } from "./SendConfig.js";
|
|
5
|
+
// Microservice
|
|
6
|
+
export { default as SMSService } from "./SMSService.js";
|
|
7
|
+
// Queue & Scheduler
|
|
8
|
+
export { default as SMSQueue } from "./SMSQueue.js";
|
|
9
|
+
export { default as SMSScheduler } from "./SMSScheduler.js";
|
|
10
|
+
// Plugins
|
|
11
|
+
export { default as PluginManager, LoggerPlugin, MetricsPlugin, } from "./PluginManager.js";
|
|
12
|
+
// Credential provisioning
|
|
13
|
+
export { default as CredentialProvisioner, } from "./CredentialProvisioner.js";
|
|
14
|
+
// Lower-level building blocks (advanced use)
|
|
15
|
+
export { default as CircuitBreaker } from "./CircuitBreaker.js";
|
|
16
|
+
export { default as RetryHandler } from "./RetryHandler.js";
|
|
17
|
+
export { default as MessageFormatter } from "./MessageFormatter.js";
|
|
18
|
+
// Errors
|
|
19
|
+
export { ServerError, ValidationError, TemplateError, NetworkError, RateLimitError, CircuitOpenError, } from "./errors.js";
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type Result<T> = {
|
|
2
|
+
ok: true;
|
|
3
|
+
data: T;
|
|
4
|
+
error: null;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
data: null;
|
|
8
|
+
error: ErrorLog;
|
|
9
|
+
};
|
|
10
|
+
export interface ErrorLog {
|
|
11
|
+
name: string;
|
|
12
|
+
message: string;
|
|
13
|
+
code: string;
|
|
14
|
+
isClientError: boolean;
|
|
15
|
+
isServerError: boolean;
|
|
16
|
+
details?: unknown;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SendParams {
|
|
20
|
+
to: string;
|
|
21
|
+
template: string;
|
|
22
|
+
variables?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface SendObjectParams<T extends Record<string, unknown>> {
|
|
25
|
+
to: string;
|
|
26
|
+
object: T;
|
|
27
|
+
template?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface BatchResult<T> {
|
|
30
|
+
succeeded: Array<{
|
|
31
|
+
index: number;
|
|
32
|
+
to: string;
|
|
33
|
+
data: T;
|
|
34
|
+
}>;
|
|
35
|
+
failed: Array<{
|
|
36
|
+
index: number;
|
|
37
|
+
to: string;
|
|
38
|
+
error: ErrorLog;
|
|
39
|
+
}>;
|
|
40
|
+
total: number;
|
|
41
|
+
successCount: number;
|
|
42
|
+
failureCount: number;
|
|
43
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|