@forgezero/providers 0.1.19 → 0.1.20
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.js +62 -18
- 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/index.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.20";
|
|
302
340
|
export {
|
|
303
341
|
staticConfig,
|
|
304
342
|
serviceMethod,
|
|
@@ -307,6 +345,7 @@ export {
|
|
|
307
345
|
envCredentials,
|
|
308
346
|
defineSingleMethodProvider,
|
|
309
347
|
defineService,
|
|
348
|
+
defineProviderMethodBranches,
|
|
310
349
|
defineProviderMethod,
|
|
311
350
|
defineProvider,
|
|
312
351
|
createService,
|
package/dist/pool.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.20";
|
|
302
340
|
|
|
303
341
|
// src/http.ts
|
|
304
342
|
class BudgetExhausted extends ProviderError {
|
package/dist/storage.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.20";
|
|
302
340
|
|
|
303
341
|
// src/storage.ts
|
|
304
342
|
var encoder = new TextEncoder;
|