@forgezero/providers 0.1.19 → 0.1.21
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 +95 -38
- package/dist/binance.js +56 -18
- package/dist/chain.js +56 -18
- package/dist/database.js +56 -18
- package/dist/email.d.ts +4 -0
- package/dist/email.js +80 -22
- package/dist/http.js +56 -18
- package/dist/index.d.ts +21 -3
- package/dist/index.js +57 -18
- package/dist/pool.js +56 -18
- package/dist/storage.js +56 -18
- package/dist/translation.js +56 -18
- package/package.json +1 -1
package/dist/database.js
CHANGED
|
@@ -75,25 +75,55 @@ function staticConfig(config) {
|
|
|
75
75
|
async list(serviceKey, methodKey) {
|
|
76
76
|
return (config.services[serviceKey]?.[methodKey] ?? []).map((attachment) => ({
|
|
77
77
|
...attachment,
|
|
78
|
-
health: health.get(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}`) ?? attachment.health
|
|
78
|
+
health: health.get(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}:${attachment.providerMethodVersion ?? "current"}`) ?? (attachment.providerMethodVersion === undefined ? [...health.entries()].find(([key]) => key.startsWith(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}:`))?.[1] : undefined) ?? attachment.health
|
|
79
79
|
}));
|
|
80
80
|
},
|
|
81
|
-
async recordHealth(serviceKey, methodKey, instanceKey, providerMethod, next) {
|
|
82
|
-
health.set(`${serviceKey}:${methodKey}:${instanceKey}:${providerMethod}`, next);
|
|
81
|
+
async recordHealth(serviceKey, methodKey, instanceKey, providerMethod, providerMethodVersion, next) {
|
|
82
|
+
health.set(`${serviceKey}:${methodKey}:${instanceKey}:${providerMethod}:${providerMethodVersion}`, next);
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
function defineProviderMethod(method) {
|
|
87
87
|
return method;
|
|
88
88
|
}
|
|
89
|
+
function defineProviderMethodBranches(branches) {
|
|
90
|
+
const entries = Object.entries(branches.versions);
|
|
91
|
+
if (entries.length === 0 || !branches.versions[branches.currentVersion]) {
|
|
92
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", "A versioned provider method needs a current branch.");
|
|
93
|
+
}
|
|
94
|
+
for (const [version, method] of entries) {
|
|
95
|
+
if (method.version !== version) {
|
|
96
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Provider method branch "${version}" declares version "${method.version}".`);
|
|
97
|
+
}
|
|
98
|
+
if (version === branches.currentVersion && method.lifecycle !== "current") {
|
|
99
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Current provider method branch "${version}" must have lifecycle current.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return branches;
|
|
103
|
+
}
|
|
104
|
+
var methodBranch = (provider, methodName, version) => {
|
|
105
|
+
const entry = provider?.methods[methodName];
|
|
106
|
+
if (!entry)
|
|
107
|
+
return;
|
|
108
|
+
if ("versions" in entry)
|
|
109
|
+
return entry.versions[version ?? entry.currentVersion];
|
|
110
|
+
return version === undefined || version === entry.version ? entry : undefined;
|
|
111
|
+
};
|
|
89
112
|
function defineProvider(spec) {
|
|
113
|
+
for (const [name, entry] of Object.entries(spec.methods)) {
|
|
114
|
+
if ("versions" in entry)
|
|
115
|
+
defineProviderMethodBranches(entry);
|
|
116
|
+
else if (!entry.version || !["current", "legacy", "deprecated", "retired"].includes(entry.lifecycle)) {
|
|
117
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Provider method "${spec.id}.${name}" needs a version and lifecycle.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
90
120
|
return spec;
|
|
91
121
|
}
|
|
92
122
|
function defineSingleMethodProvider(spec) {
|
|
93
|
-
const { method, invoke, classify, ...identity } = spec;
|
|
123
|
+
const { method, version = "v1", lifecycle = "current", invoke, classify, ...identity } = spec;
|
|
94
124
|
return defineProvider({
|
|
95
125
|
...identity,
|
|
96
|
-
methods: { [method]: defineProviderMethod({ invoke, classify }) }
|
|
126
|
+
methods: { [method]: defineProviderMethod({ version, lifecycle, invoke, classify }) }
|
|
97
127
|
});
|
|
98
128
|
}
|
|
99
129
|
var STRIKES_TO_OFFLINE = 3;
|
|
@@ -121,6 +151,7 @@ function createRegistry(options) {
|
|
|
121
151
|
const configured = [...await options.config.list(serviceKey, methodKey)].filter((attachment) => attachment.enabled).sort((a, b) => a.priority - b.priority);
|
|
122
152
|
const attempts = [];
|
|
123
153
|
for (const attachment of configured) {
|
|
154
|
+
const requestedVersion = attachment.providerMethodVersion;
|
|
124
155
|
if (callOptions.signal?.aborted) {
|
|
125
156
|
const cancelled = {
|
|
126
157
|
ok: false,
|
|
@@ -133,21 +164,25 @@ function createRegistry(options) {
|
|
|
133
164
|
}
|
|
134
165
|
const instance = await options.config.provider(attachment.instanceKey);
|
|
135
166
|
if (!instance) {
|
|
136
|
-
attempts.push({ providerId: "unknown", instanceKey: attachment.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "instance not registered" });
|
|
167
|
+
attempts.push({ providerId: "unknown", instanceKey: attachment.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: requestedVersion ?? "current", outcome: "skipped", error: "instance not registered" });
|
|
137
168
|
continue;
|
|
138
169
|
}
|
|
139
170
|
const spec = byId.get(instance.providerId);
|
|
140
|
-
const method = spec
|
|
171
|
+
const method = methodBranch(spec, attachment.providerMethod, requestedVersion);
|
|
141
172
|
if (!spec || !method) {
|
|
142
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: !spec ? "provider not registered" : "method not supported" });
|
|
173
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: requestedVersion ?? "current", outcome: "skipped", error: !spec ? "provider not registered" : "method version not supported" });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (method.lifecycle === "retired") {
|
|
177
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "method version retired" });
|
|
143
178
|
continue;
|
|
144
179
|
}
|
|
145
180
|
if (!instance.enabled) {
|
|
146
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "instance disabled" });
|
|
181
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "instance disabled" });
|
|
147
182
|
continue;
|
|
148
183
|
}
|
|
149
184
|
if (attachment.health?.status === "offline") {
|
|
150
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "offline" });
|
|
185
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "offline" });
|
|
151
186
|
continue;
|
|
152
187
|
}
|
|
153
188
|
await options.before?.({ service: serviceKey, method: methodKey, provider: instance.providerId, instance: instance.instanceKey });
|
|
@@ -158,15 +193,15 @@ function createRegistry(options) {
|
|
|
158
193
|
secret: (field) => options.credentials.get(instance.secretRef, field),
|
|
159
194
|
signal: callOptions.signal
|
|
160
195
|
}, args);
|
|
161
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "sent", durationMs: performance.now() - startedAt });
|
|
162
|
-
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, nextHealth(attachment.health, "success"));
|
|
196
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "sent", durationMs: performance.now() - startedAt });
|
|
197
|
+
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, method.version, nextHealth(attachment.health, "success"));
|
|
163
198
|
const sent = {
|
|
164
199
|
ok: true,
|
|
165
200
|
result,
|
|
166
201
|
provider: instance.providerId,
|
|
167
202
|
instance: instance.instanceKey,
|
|
168
203
|
method: attachment.providerMethod,
|
|
169
|
-
selected: { providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod },
|
|
204
|
+
selected: { providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version },
|
|
170
205
|
fallbackUsed: attempts.length > 1,
|
|
171
206
|
attempts
|
|
172
207
|
};
|
|
@@ -190,13 +225,14 @@ function createRegistry(options) {
|
|
|
190
225
|
providerId: instance.providerId,
|
|
191
226
|
instanceKey: instance.instanceKey,
|
|
192
227
|
providerMethod: attachment.providerMethod,
|
|
228
|
+
providerMethodVersion: method.version,
|
|
193
229
|
outcome: "failed",
|
|
194
230
|
kind,
|
|
195
231
|
durationMs: performance.now() - startedAt,
|
|
196
232
|
failure: { kind, ...code ? { code } : {}, message },
|
|
197
233
|
error: message
|
|
198
234
|
});
|
|
199
|
-
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, nextHealth(attachment.health, kind));
|
|
235
|
+
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, method.version, nextHealth(attachment.health, kind));
|
|
200
236
|
if (kind === "terminal") {
|
|
201
237
|
const refused = {
|
|
202
238
|
ok: false,
|
|
@@ -246,8 +282,9 @@ function createService(definition, methods, options = {}) {
|
|
|
246
282
|
throw new ProviderError("CONFIG_PRIORITY_DUPLICATE", `Priorities for "${definition.key}.${methodKey}" must be unique.`);
|
|
247
283
|
}
|
|
248
284
|
priorities.add(attachment.priority);
|
|
249
|
-
|
|
250
|
-
|
|
285
|
+
const branch = methodBranch(attachment.provider, attachment.method, attachment.version);
|
|
286
|
+
if (!branch || branch.lifecycle === "retired") {
|
|
287
|
+
throw new ProviderError("CONFIG_METHOD_UNSUPPORTED", `Provider "${attachment.provider.id}" does not support active method "${attachment.method}@${attachment.version ?? "current"}".`);
|
|
251
288
|
}
|
|
252
289
|
const existing = providers.get(attachment.provider.id);
|
|
253
290
|
if (existing && existing !== attachment.provider) {
|
|
@@ -266,6 +303,7 @@ function createService(definition, methods, options = {}) {
|
|
|
266
303
|
return {
|
|
267
304
|
instanceKey: internalKey,
|
|
268
305
|
providerMethod: attachment.method,
|
|
306
|
+
providerMethodVersion: branch.version,
|
|
269
307
|
priority: attachment.priority,
|
|
270
308
|
enabled: attachment.enabled ?? true
|
|
271
309
|
};
|
|
@@ -292,13 +330,13 @@ function createService(definition, methods, options = {}) {
|
|
|
292
330
|
const { instance: _instance, selected, attempts, ...rest } = result;
|
|
293
331
|
return {
|
|
294
332
|
...rest,
|
|
295
|
-
...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod } } : {},
|
|
333
|
+
...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod, providerMethodVersion: selected.providerMethodVersion } } : {},
|
|
296
334
|
attempts: attempts.map(({ instanceKey: _instanceKey, ...attempt }) => attempt)
|
|
297
335
|
};
|
|
298
336
|
}
|
|
299
337
|
};
|
|
300
338
|
}
|
|
301
|
-
var VERSION = "0.1.
|
|
339
|
+
var VERSION = "0.1.21";
|
|
302
340
|
|
|
303
341
|
// src/database.ts
|
|
304
342
|
var pools = new Map;
|
package/dist/email.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { type EmailBatch, type EmailBatchResult, type EmailMessage } from './ind
|
|
|
2
2
|
export interface JetEmailConfig {
|
|
3
3
|
/** EU residency endpoint. */
|
|
4
4
|
eu?: boolean;
|
|
5
|
+
/** Bare sender mailbox. JetEmail's wire API receives it as `Name <mailbox>`. */
|
|
6
|
+
from?: string;
|
|
7
|
+
/** Optional display name. Defaults to the sender mailbox local part. */
|
|
8
|
+
fromName?: string;
|
|
5
9
|
endpoint?: string;
|
|
6
10
|
batchEndpoint?: string;
|
|
7
11
|
fetch?: typeof globalThis.fetch;
|
package/dist/email.js
CHANGED
|
@@ -75,25 +75,55 @@ function staticConfig(config) {
|
|
|
75
75
|
async list(serviceKey, methodKey) {
|
|
76
76
|
return (config.services[serviceKey]?.[methodKey] ?? []).map((attachment) => ({
|
|
77
77
|
...attachment,
|
|
78
|
-
health: health.get(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}`) ?? attachment.health
|
|
78
|
+
health: health.get(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}:${attachment.providerMethodVersion ?? "current"}`) ?? (attachment.providerMethodVersion === undefined ? [...health.entries()].find(([key]) => key.startsWith(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}:`))?.[1] : undefined) ?? attachment.health
|
|
79
79
|
}));
|
|
80
80
|
},
|
|
81
|
-
async recordHealth(serviceKey, methodKey, instanceKey, providerMethod, next) {
|
|
82
|
-
health.set(`${serviceKey}:${methodKey}:${instanceKey}:${providerMethod}`, next);
|
|
81
|
+
async recordHealth(serviceKey, methodKey, instanceKey, providerMethod, providerMethodVersion, next) {
|
|
82
|
+
health.set(`${serviceKey}:${methodKey}:${instanceKey}:${providerMethod}:${providerMethodVersion}`, next);
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
function defineProviderMethod(method) {
|
|
87
87
|
return method;
|
|
88
88
|
}
|
|
89
|
+
function defineProviderMethodBranches(branches) {
|
|
90
|
+
const entries = Object.entries(branches.versions);
|
|
91
|
+
if (entries.length === 0 || !branches.versions[branches.currentVersion]) {
|
|
92
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", "A versioned provider method needs a current branch.");
|
|
93
|
+
}
|
|
94
|
+
for (const [version, method] of entries) {
|
|
95
|
+
if (method.version !== version) {
|
|
96
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Provider method branch "${version}" declares version "${method.version}".`);
|
|
97
|
+
}
|
|
98
|
+
if (version === branches.currentVersion && method.lifecycle !== "current") {
|
|
99
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Current provider method branch "${version}" must have lifecycle current.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return branches;
|
|
103
|
+
}
|
|
104
|
+
var methodBranch = (provider, methodName, version) => {
|
|
105
|
+
const entry = provider?.methods[methodName];
|
|
106
|
+
if (!entry)
|
|
107
|
+
return;
|
|
108
|
+
if ("versions" in entry)
|
|
109
|
+
return entry.versions[version ?? entry.currentVersion];
|
|
110
|
+
return version === undefined || version === entry.version ? entry : undefined;
|
|
111
|
+
};
|
|
89
112
|
function defineProvider(spec) {
|
|
113
|
+
for (const [name, entry] of Object.entries(spec.methods)) {
|
|
114
|
+
if ("versions" in entry)
|
|
115
|
+
defineProviderMethodBranches(entry);
|
|
116
|
+
else if (!entry.version || !["current", "legacy", "deprecated", "retired"].includes(entry.lifecycle)) {
|
|
117
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Provider method "${spec.id}.${name}" needs a version and lifecycle.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
90
120
|
return spec;
|
|
91
121
|
}
|
|
92
122
|
function defineSingleMethodProvider(spec) {
|
|
93
|
-
const { method, invoke, classify, ...identity } = spec;
|
|
123
|
+
const { method, version = "v1", lifecycle = "current", invoke, classify, ...identity } = spec;
|
|
94
124
|
return defineProvider({
|
|
95
125
|
...identity,
|
|
96
|
-
methods: { [method]: defineProviderMethod({ invoke, classify }) }
|
|
126
|
+
methods: { [method]: defineProviderMethod({ version, lifecycle, invoke, classify }) }
|
|
97
127
|
});
|
|
98
128
|
}
|
|
99
129
|
var STRIKES_TO_OFFLINE = 3;
|
|
@@ -121,6 +151,7 @@ function createRegistry(options) {
|
|
|
121
151
|
const configured = [...await options.config.list(serviceKey, methodKey)].filter((attachment) => attachment.enabled).sort((a, b) => a.priority - b.priority);
|
|
122
152
|
const attempts = [];
|
|
123
153
|
for (const attachment of configured) {
|
|
154
|
+
const requestedVersion = attachment.providerMethodVersion;
|
|
124
155
|
if (callOptions.signal?.aborted) {
|
|
125
156
|
const cancelled = {
|
|
126
157
|
ok: false,
|
|
@@ -133,21 +164,25 @@ function createRegistry(options) {
|
|
|
133
164
|
}
|
|
134
165
|
const instance = await options.config.provider(attachment.instanceKey);
|
|
135
166
|
if (!instance) {
|
|
136
|
-
attempts.push({ providerId: "unknown", instanceKey: attachment.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "instance not registered" });
|
|
167
|
+
attempts.push({ providerId: "unknown", instanceKey: attachment.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: requestedVersion ?? "current", outcome: "skipped", error: "instance not registered" });
|
|
137
168
|
continue;
|
|
138
169
|
}
|
|
139
170
|
const spec = byId.get(instance.providerId);
|
|
140
|
-
const method = spec
|
|
171
|
+
const method = methodBranch(spec, attachment.providerMethod, requestedVersion);
|
|
141
172
|
if (!spec || !method) {
|
|
142
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: !spec ? "provider not registered" : "method not supported" });
|
|
173
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: requestedVersion ?? "current", outcome: "skipped", error: !spec ? "provider not registered" : "method version not supported" });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (method.lifecycle === "retired") {
|
|
177
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "method version retired" });
|
|
143
178
|
continue;
|
|
144
179
|
}
|
|
145
180
|
if (!instance.enabled) {
|
|
146
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "instance disabled" });
|
|
181
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "instance disabled" });
|
|
147
182
|
continue;
|
|
148
183
|
}
|
|
149
184
|
if (attachment.health?.status === "offline") {
|
|
150
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "offline" });
|
|
185
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "offline" });
|
|
151
186
|
continue;
|
|
152
187
|
}
|
|
153
188
|
await options.before?.({ service: serviceKey, method: methodKey, provider: instance.providerId, instance: instance.instanceKey });
|
|
@@ -158,15 +193,15 @@ function createRegistry(options) {
|
|
|
158
193
|
secret: (field) => options.credentials.get(instance.secretRef, field),
|
|
159
194
|
signal: callOptions.signal
|
|
160
195
|
}, args);
|
|
161
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "sent", durationMs: performance.now() - startedAt });
|
|
162
|
-
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, nextHealth(attachment.health, "success"));
|
|
196
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "sent", durationMs: performance.now() - startedAt });
|
|
197
|
+
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, method.version, nextHealth(attachment.health, "success"));
|
|
163
198
|
const sent = {
|
|
164
199
|
ok: true,
|
|
165
200
|
result,
|
|
166
201
|
provider: instance.providerId,
|
|
167
202
|
instance: instance.instanceKey,
|
|
168
203
|
method: attachment.providerMethod,
|
|
169
|
-
selected: { providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod },
|
|
204
|
+
selected: { providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version },
|
|
170
205
|
fallbackUsed: attempts.length > 1,
|
|
171
206
|
attempts
|
|
172
207
|
};
|
|
@@ -190,13 +225,14 @@ function createRegistry(options) {
|
|
|
190
225
|
providerId: instance.providerId,
|
|
191
226
|
instanceKey: instance.instanceKey,
|
|
192
227
|
providerMethod: attachment.providerMethod,
|
|
228
|
+
providerMethodVersion: method.version,
|
|
193
229
|
outcome: "failed",
|
|
194
230
|
kind,
|
|
195
231
|
durationMs: performance.now() - startedAt,
|
|
196
232
|
failure: { kind, ...code ? { code } : {}, message },
|
|
197
233
|
error: message
|
|
198
234
|
});
|
|
199
|
-
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, nextHealth(attachment.health, kind));
|
|
235
|
+
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, method.version, nextHealth(attachment.health, kind));
|
|
200
236
|
if (kind === "terminal") {
|
|
201
237
|
const refused = {
|
|
202
238
|
ok: false,
|
|
@@ -246,8 +282,9 @@ function createService(definition, methods, options = {}) {
|
|
|
246
282
|
throw new ProviderError("CONFIG_PRIORITY_DUPLICATE", `Priorities for "${definition.key}.${methodKey}" must be unique.`);
|
|
247
283
|
}
|
|
248
284
|
priorities.add(attachment.priority);
|
|
249
|
-
|
|
250
|
-
|
|
285
|
+
const branch = methodBranch(attachment.provider, attachment.method, attachment.version);
|
|
286
|
+
if (!branch || branch.lifecycle === "retired") {
|
|
287
|
+
throw new ProviderError("CONFIG_METHOD_UNSUPPORTED", `Provider "${attachment.provider.id}" does not support active method "${attachment.method}@${attachment.version ?? "current"}".`);
|
|
251
288
|
}
|
|
252
289
|
const existing = providers.get(attachment.provider.id);
|
|
253
290
|
if (existing && existing !== attachment.provider) {
|
|
@@ -266,6 +303,7 @@ function createService(definition, methods, options = {}) {
|
|
|
266
303
|
return {
|
|
267
304
|
instanceKey: internalKey,
|
|
268
305
|
providerMethod: attachment.method,
|
|
306
|
+
providerMethodVersion: branch.version,
|
|
269
307
|
priority: attachment.priority,
|
|
270
308
|
enabled: attachment.enabled ?? true
|
|
271
309
|
};
|
|
@@ -292,13 +330,13 @@ function createService(definition, methods, options = {}) {
|
|
|
292
330
|
const { instance: _instance, selected, attempts, ...rest } = result;
|
|
293
331
|
return {
|
|
294
332
|
...rest,
|
|
295
|
-
...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod } } : {},
|
|
333
|
+
...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod, providerMethodVersion: selected.providerMethodVersion } } : {},
|
|
296
334
|
attempts: attempts.map(({ instanceKey: _instanceKey, ...attempt }) => attempt)
|
|
297
335
|
};
|
|
298
336
|
}
|
|
299
337
|
};
|
|
300
338
|
}
|
|
301
|
-
var VERSION = "0.1.
|
|
339
|
+
var VERSION = "0.1.21";
|
|
302
340
|
|
|
303
341
|
// src/email.ts
|
|
304
342
|
var recipients = (to) => Array.isArray(to) ? [...to] : [to];
|
|
@@ -310,6 +348,19 @@ function assertMessage(message) {
|
|
|
310
348
|
throw new ProviderError("EMAIL_NO_BODY", "An email needs html, text, or both.");
|
|
311
349
|
}
|
|
312
350
|
}
|
|
351
|
+
var JETEMAIL_MAILBOX = /^[^\s<>@]+@[^\s<>@]+\.[^\s<>@]+$/;
|
|
352
|
+
function jetemailSender(config, override) {
|
|
353
|
+
const mailbox = (override ?? config.from ?? "").trim();
|
|
354
|
+
if (!JETEMAIL_MAILBOX.test(mailbox)) {
|
|
355
|
+
throw new ProviderError("JETEMAIL_FROM", "JetEmail needs a valid sender mailbox.");
|
|
356
|
+
}
|
|
357
|
+
const fallbackName = mailbox.slice(0, mailbox.indexOf("@"));
|
|
358
|
+
const name = (config.fromName ?? fallbackName).trim();
|
|
359
|
+
if (!name || /[\r\n<>]/.test(name)) {
|
|
360
|
+
throw new ProviderError("JETEMAIL_FROM_NAME", "JetEmail sender name must be non-empty and header-safe.");
|
|
361
|
+
}
|
|
362
|
+
return `${name} <${mailbox}>`;
|
|
363
|
+
}
|
|
313
364
|
var jetemailCredentials = {
|
|
314
365
|
type: "object",
|
|
315
366
|
additionalProperties: false,
|
|
@@ -328,10 +379,13 @@ var jetemailConfig = {
|
|
|
328
379
|
additionalProperties: false,
|
|
329
380
|
properties: {
|
|
330
381
|
eu: { type: "boolean", default: false, title: "EU residency" },
|
|
331
|
-
from: { type: "string", format: "email", title: "Default from address" }
|
|
382
|
+
from: { type: "string", format: "email", title: "Default from address" },
|
|
383
|
+
fromName: { type: "string", minLength: 1, maxLength: 120, title: "Sender name" }
|
|
332
384
|
}
|
|
333
385
|
};
|
|
334
386
|
var jetemailSend = defineProviderMethod({
|
|
387
|
+
version: "v1",
|
|
388
|
+
lifecycle: "current",
|
|
335
389
|
async invoke(context, message) {
|
|
336
390
|
assertMessage(message);
|
|
337
391
|
const config = context.config;
|
|
@@ -346,7 +400,7 @@ var jetemailSend = defineProviderMethod({
|
|
|
346
400
|
...message.idempotencyKey ? { "idempotency-key": message.idempotencyKey } : {}
|
|
347
401
|
},
|
|
348
402
|
body: JSON.stringify({
|
|
349
|
-
from: message.from
|
|
403
|
+
from: jetemailSender(config, message.from),
|
|
350
404
|
to: recipients(message.to),
|
|
351
405
|
subject: message.subject,
|
|
352
406
|
html: message.html,
|
|
@@ -355,7 +409,7 @@ var jetemailSend = defineProviderMethod({
|
|
|
355
409
|
eu: config.eu ?? false
|
|
356
410
|
})
|
|
357
411
|
});
|
|
358
|
-
if (response.status === 201 || response.status === 202) {
|
|
412
|
+
if (response.status === 200 || response.status === 201 || response.status === 202) {
|
|
359
413
|
const payload = await response.json().catch(() => ({}));
|
|
360
414
|
return { id: payload.id ?? "" };
|
|
361
415
|
}
|
|
@@ -382,6 +436,8 @@ function assertBatch(batch) {
|
|
|
382
436
|
assertMessage(message);
|
|
383
437
|
}
|
|
384
438
|
var jetemailSendBatch = defineProviderMethod({
|
|
439
|
+
version: "v1",
|
|
440
|
+
lifecycle: "current",
|
|
385
441
|
async invoke(context, batch) {
|
|
386
442
|
assertBatch(batch);
|
|
387
443
|
const config = context.config;
|
|
@@ -395,7 +451,7 @@ var jetemailSendBatch = defineProviderMethod({
|
|
|
395
451
|
},
|
|
396
452
|
body: JSON.stringify({
|
|
397
453
|
emails: batch.emails.map((message) => ({
|
|
398
|
-
from: message.from
|
|
454
|
+
from: jetemailSender(config, message.from),
|
|
399
455
|
to: recipients(message.to),
|
|
400
456
|
subject: message.subject,
|
|
401
457
|
html: message.html,
|
|
@@ -446,6 +502,8 @@ var jetemail = defineProvider({
|
|
|
446
502
|
}
|
|
447
503
|
});
|
|
448
504
|
var smtpSend = defineProviderMethod({
|
|
505
|
+
version: "rfc5321",
|
|
506
|
+
lifecycle: "current",
|
|
449
507
|
async invoke(context, message) {
|
|
450
508
|
assertMessage(message);
|
|
451
509
|
const config = context.config;
|
package/dist/http.js
CHANGED
|
@@ -75,25 +75,55 @@ function staticConfig(config) {
|
|
|
75
75
|
async list(serviceKey, methodKey) {
|
|
76
76
|
return (config.services[serviceKey]?.[methodKey] ?? []).map((attachment) => ({
|
|
77
77
|
...attachment,
|
|
78
|
-
health: health.get(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}`) ?? attachment.health
|
|
78
|
+
health: health.get(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}:${attachment.providerMethodVersion ?? "current"}`) ?? (attachment.providerMethodVersion === undefined ? [...health.entries()].find(([key]) => key.startsWith(`${serviceKey}:${methodKey}:${attachment.instanceKey}:${attachment.providerMethod}:`))?.[1] : undefined) ?? attachment.health
|
|
79
79
|
}));
|
|
80
80
|
},
|
|
81
|
-
async recordHealth(serviceKey, methodKey, instanceKey, providerMethod, next) {
|
|
82
|
-
health.set(`${serviceKey}:${methodKey}:${instanceKey}:${providerMethod}`, next);
|
|
81
|
+
async recordHealth(serviceKey, methodKey, instanceKey, providerMethod, providerMethodVersion, next) {
|
|
82
|
+
health.set(`${serviceKey}:${methodKey}:${instanceKey}:${providerMethod}:${providerMethodVersion}`, next);
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
function defineProviderMethod(method) {
|
|
87
87
|
return method;
|
|
88
88
|
}
|
|
89
|
+
function defineProviderMethodBranches(branches) {
|
|
90
|
+
const entries = Object.entries(branches.versions);
|
|
91
|
+
if (entries.length === 0 || !branches.versions[branches.currentVersion]) {
|
|
92
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", "A versioned provider method needs a current branch.");
|
|
93
|
+
}
|
|
94
|
+
for (const [version, method] of entries) {
|
|
95
|
+
if (method.version !== version) {
|
|
96
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Provider method branch "${version}" declares version "${method.version}".`);
|
|
97
|
+
}
|
|
98
|
+
if (version === branches.currentVersion && method.lifecycle !== "current") {
|
|
99
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Current provider method branch "${version}" must have lifecycle current.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return branches;
|
|
103
|
+
}
|
|
104
|
+
var methodBranch = (provider, methodName, version) => {
|
|
105
|
+
const entry = provider?.methods[methodName];
|
|
106
|
+
if (!entry)
|
|
107
|
+
return;
|
|
108
|
+
if ("versions" in entry)
|
|
109
|
+
return entry.versions[version ?? entry.currentVersion];
|
|
110
|
+
return version === undefined || version === entry.version ? entry : undefined;
|
|
111
|
+
};
|
|
89
112
|
function defineProvider(spec) {
|
|
113
|
+
for (const [name, entry] of Object.entries(spec.methods)) {
|
|
114
|
+
if ("versions" in entry)
|
|
115
|
+
defineProviderMethodBranches(entry);
|
|
116
|
+
else if (!entry.version || !["current", "legacy", "deprecated", "retired"].includes(entry.lifecycle)) {
|
|
117
|
+
throw new ProviderError("PROVIDER_METHOD_VERSION", `Provider method "${spec.id}.${name}" needs a version and lifecycle.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
90
120
|
return spec;
|
|
91
121
|
}
|
|
92
122
|
function defineSingleMethodProvider(spec) {
|
|
93
|
-
const { method, invoke, classify, ...identity } = spec;
|
|
123
|
+
const { method, version = "v1", lifecycle = "current", invoke, classify, ...identity } = spec;
|
|
94
124
|
return defineProvider({
|
|
95
125
|
...identity,
|
|
96
|
-
methods: { [method]: defineProviderMethod({ invoke, classify }) }
|
|
126
|
+
methods: { [method]: defineProviderMethod({ version, lifecycle, invoke, classify }) }
|
|
97
127
|
});
|
|
98
128
|
}
|
|
99
129
|
var STRIKES_TO_OFFLINE = 3;
|
|
@@ -121,6 +151,7 @@ function createRegistry(options) {
|
|
|
121
151
|
const configured = [...await options.config.list(serviceKey, methodKey)].filter((attachment) => attachment.enabled).sort((a, b) => a.priority - b.priority);
|
|
122
152
|
const attempts = [];
|
|
123
153
|
for (const attachment of configured) {
|
|
154
|
+
const requestedVersion = attachment.providerMethodVersion;
|
|
124
155
|
if (callOptions.signal?.aborted) {
|
|
125
156
|
const cancelled = {
|
|
126
157
|
ok: false,
|
|
@@ -133,21 +164,25 @@ function createRegistry(options) {
|
|
|
133
164
|
}
|
|
134
165
|
const instance = await options.config.provider(attachment.instanceKey);
|
|
135
166
|
if (!instance) {
|
|
136
|
-
attempts.push({ providerId: "unknown", instanceKey: attachment.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "instance not registered" });
|
|
167
|
+
attempts.push({ providerId: "unknown", instanceKey: attachment.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: requestedVersion ?? "current", outcome: "skipped", error: "instance not registered" });
|
|
137
168
|
continue;
|
|
138
169
|
}
|
|
139
170
|
const spec = byId.get(instance.providerId);
|
|
140
|
-
const method = spec
|
|
171
|
+
const method = methodBranch(spec, attachment.providerMethod, requestedVersion);
|
|
141
172
|
if (!spec || !method) {
|
|
142
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: !spec ? "provider not registered" : "method not supported" });
|
|
173
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: requestedVersion ?? "current", outcome: "skipped", error: !spec ? "provider not registered" : "method version not supported" });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (method.lifecycle === "retired") {
|
|
177
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "method version retired" });
|
|
143
178
|
continue;
|
|
144
179
|
}
|
|
145
180
|
if (!instance.enabled) {
|
|
146
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "instance disabled" });
|
|
181
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "instance disabled" });
|
|
147
182
|
continue;
|
|
148
183
|
}
|
|
149
184
|
if (attachment.health?.status === "offline") {
|
|
150
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "skipped", error: "offline" });
|
|
185
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "offline" });
|
|
151
186
|
continue;
|
|
152
187
|
}
|
|
153
188
|
await options.before?.({ service: serviceKey, method: methodKey, provider: instance.providerId, instance: instance.instanceKey });
|
|
@@ -158,15 +193,15 @@ function createRegistry(options) {
|
|
|
158
193
|
secret: (field) => options.credentials.get(instance.secretRef, field),
|
|
159
194
|
signal: callOptions.signal
|
|
160
195
|
}, args);
|
|
161
|
-
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, outcome: "sent", durationMs: performance.now() - startedAt });
|
|
162
|
-
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, nextHealth(attachment.health, "success"));
|
|
196
|
+
attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "sent", durationMs: performance.now() - startedAt });
|
|
197
|
+
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, method.version, nextHealth(attachment.health, "success"));
|
|
163
198
|
const sent = {
|
|
164
199
|
ok: true,
|
|
165
200
|
result,
|
|
166
201
|
provider: instance.providerId,
|
|
167
202
|
instance: instance.instanceKey,
|
|
168
203
|
method: attachment.providerMethod,
|
|
169
|
-
selected: { providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod },
|
|
204
|
+
selected: { providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version },
|
|
170
205
|
fallbackUsed: attempts.length > 1,
|
|
171
206
|
attempts
|
|
172
207
|
};
|
|
@@ -190,13 +225,14 @@ function createRegistry(options) {
|
|
|
190
225
|
providerId: instance.providerId,
|
|
191
226
|
instanceKey: instance.instanceKey,
|
|
192
227
|
providerMethod: attachment.providerMethod,
|
|
228
|
+
providerMethodVersion: method.version,
|
|
193
229
|
outcome: "failed",
|
|
194
230
|
kind,
|
|
195
231
|
durationMs: performance.now() - startedAt,
|
|
196
232
|
failure: { kind, ...code ? { code } : {}, message },
|
|
197
233
|
error: message
|
|
198
234
|
});
|
|
199
|
-
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, nextHealth(attachment.health, kind));
|
|
235
|
+
await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, method.version, nextHealth(attachment.health, kind));
|
|
200
236
|
if (kind === "terminal") {
|
|
201
237
|
const refused = {
|
|
202
238
|
ok: false,
|
|
@@ -246,8 +282,9 @@ function createService(definition, methods, options = {}) {
|
|
|
246
282
|
throw new ProviderError("CONFIG_PRIORITY_DUPLICATE", `Priorities for "${definition.key}.${methodKey}" must be unique.`);
|
|
247
283
|
}
|
|
248
284
|
priorities.add(attachment.priority);
|
|
249
|
-
|
|
250
|
-
|
|
285
|
+
const branch = methodBranch(attachment.provider, attachment.method, attachment.version);
|
|
286
|
+
if (!branch || branch.lifecycle === "retired") {
|
|
287
|
+
throw new ProviderError("CONFIG_METHOD_UNSUPPORTED", `Provider "${attachment.provider.id}" does not support active method "${attachment.method}@${attachment.version ?? "current"}".`);
|
|
251
288
|
}
|
|
252
289
|
const existing = providers.get(attachment.provider.id);
|
|
253
290
|
if (existing && existing !== attachment.provider) {
|
|
@@ -266,6 +303,7 @@ function createService(definition, methods, options = {}) {
|
|
|
266
303
|
return {
|
|
267
304
|
instanceKey: internalKey,
|
|
268
305
|
providerMethod: attachment.method,
|
|
306
|
+
providerMethodVersion: branch.version,
|
|
269
307
|
priority: attachment.priority,
|
|
270
308
|
enabled: attachment.enabled ?? true
|
|
271
309
|
};
|
|
@@ -292,13 +330,13 @@ function createService(definition, methods, options = {}) {
|
|
|
292
330
|
const { instance: _instance, selected, attempts, ...rest } = result;
|
|
293
331
|
return {
|
|
294
332
|
...rest,
|
|
295
|
-
...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod } } : {},
|
|
333
|
+
...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod, providerMethodVersion: selected.providerMethodVersion } } : {},
|
|
296
334
|
attempts: attempts.map(({ instanceKey: _instanceKey, ...attempt }) => attempt)
|
|
297
335
|
};
|
|
298
336
|
}
|
|
299
337
|
};
|
|
300
338
|
}
|
|
301
|
-
var VERSION = "0.1.
|
|
339
|
+
var VERSION = "0.1.21";
|
|
302
340
|
|
|
303
341
|
// src/http.ts
|
|
304
342
|
class BudgetExhausted extends ProviderError {
|