@forgezero/providers 0.1.10 → 0.1.11

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/dist/database.js CHANGED
@@ -38,13 +38,39 @@ function chainCredentials(...sources) {
38
38
  }
39
39
  };
40
40
  }
41
+ function scopeCredentials(source, reference) {
42
+ return {
43
+ name: `${source.name}:${reference}`,
44
+ get: (field) => source.get(reference, field)
45
+ };
46
+ }
47
+ function chainScopedCredentials(...sources) {
48
+ return {
49
+ name: sources.map((source) => source.name).join("+"),
50
+ async get(field) {
51
+ let last;
52
+ for (const source of sources) {
53
+ try {
54
+ return await source.get(field);
55
+ } catch (error) {
56
+ last = error;
57
+ }
58
+ }
59
+ throw last instanceof Error ? last : new ProviderError("CREDENTIAL_MISSING", `No scoped source held field "${field}".`);
60
+ }
61
+ };
62
+ }
41
63
  function staticConfig(config) {
64
+ const providers = new Map(config.providers.map((provider) => [provider.instanceKey, provider]));
65
+ if (providers.size !== config.providers.length) {
66
+ throw new ProviderError("CONFIG_DUPLICATE_INSTANCE", "Provider instance keys must be unique.");
67
+ }
42
68
  const health = new Map;
43
69
  return {
44
70
  name: "static",
45
71
  async provider(instanceKey) {
46
- const provider = config.providers[instanceKey];
47
- return provider ? { instanceKey, ...provider, config: { ...provider.config } } : undefined;
72
+ const provider = providers.get(instanceKey);
73
+ return provider ? { ...provider, config: { ...provider.config } } : undefined;
48
74
  },
49
75
  async list(serviceKey, methodKey) {
50
76
  return (config.services[serviceKey]?.[methodKey] ?? []).map((attachment) => ({
@@ -201,7 +227,78 @@ function createRegistry(options) {
201
227
  }
202
228
  return { call, service };
203
229
  }
204
- var VERSION = "0.1.10";
230
+ function createService(definition, methods, options = {}) {
231
+ const providers = new Map;
232
+ const credentials = new Map;
233
+ const instances = [];
234
+ const serviceMethods = {};
235
+ for (const methodKey of Object.keys(definition.methods)) {
236
+ const attachments = methods[methodKey];
237
+ if (!Array.isArray(attachments)) {
238
+ throw new ProviderError("CONFIG_METHOD_MISSING", `Service method "${definition.key}.${methodKey}" needs an attachment array.`);
239
+ }
240
+ const priorities = new Set;
241
+ serviceMethods[methodKey] = attachments.map((attachment, index) => {
242
+ if (!Number.isInteger(attachment.priority) || attachment.priority < 1 || attachment.priority > 1000) {
243
+ throw new ProviderError("CONFIG_PRIORITY", `Priority for "${definition.key}.${methodKey}" must be an integer from 1 to 1000.`);
244
+ }
245
+ if (priorities.has(attachment.priority)) {
246
+ throw new ProviderError("CONFIG_PRIORITY_DUPLICATE", `Priorities for "${definition.key}.${methodKey}" must be unique.`);
247
+ }
248
+ priorities.add(attachment.priority);
249
+ if (!attachment.provider.methods[attachment.method]) {
250
+ throw new ProviderError("CONFIG_METHOD_UNSUPPORTED", `Provider "${attachment.provider.id}" does not support "${attachment.method}".`);
251
+ }
252
+ const existing = providers.get(attachment.provider.id);
253
+ if (existing && existing !== attachment.provider) {
254
+ throw new ProviderError("CONFIG_PROVIDER_DUPLICATE", `Provider id "${attachment.provider.id}" has more than one definition.`);
255
+ }
256
+ providers.set(attachment.provider.id, attachment.provider);
257
+ const internalKey = `${definition.key}:${methodKey}:${index}`;
258
+ credentials.set(internalKey, attachment.credentials);
259
+ instances.push({
260
+ instanceKey: internalKey,
261
+ providerId: attachment.provider.id,
262
+ enabled: attachment.enabled ?? true,
263
+ config: { ...attachment.config ?? {} },
264
+ secretRef: internalKey
265
+ });
266
+ return {
267
+ instanceKey: internalKey,
268
+ providerMethod: attachment.method,
269
+ priority: attachment.priority,
270
+ enabled: attachment.enabled ?? true
271
+ };
272
+ });
273
+ }
274
+ const registry = createRegistry({
275
+ providers: [...providers.values()],
276
+ config: staticConfig({ providers: instances, services: { [definition.key]: serviceMethods } }),
277
+ credentials: {
278
+ name: [...new Set([...credentials.values()].map((source) => source.name))].join("+"),
279
+ async get(reference, field) {
280
+ const source = credentials.get(reference);
281
+ if (!source)
282
+ throw new ProviderError("CREDENTIAL_SOURCE_MISSING", "The provider credential source is unavailable.");
283
+ return source.get(field);
284
+ }
285
+ },
286
+ ...options
287
+ });
288
+ const dynamic = registry.service(definition);
289
+ return {
290
+ async call(method, args, callOptions) {
291
+ const result = await dynamic.call(method, args, callOptions);
292
+ const { instance: _instance, selected, attempts, ...rest } = result;
293
+ return {
294
+ ...rest,
295
+ ...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod } } : {},
296
+ attempts: attempts.map(({ instanceKey: _instanceKey, ...attempt }) => attempt)
297
+ };
298
+ }
299
+ };
300
+ }
301
+ var VERSION = "0.1.11";
205
302
 
206
303
  // src/database.ts
207
304
  var pools = new Map;
package/dist/email.js CHANGED
@@ -38,13 +38,39 @@ function chainCredentials(...sources) {
38
38
  }
39
39
  };
40
40
  }
41
+ function scopeCredentials(source, reference) {
42
+ return {
43
+ name: `${source.name}:${reference}`,
44
+ get: (field) => source.get(reference, field)
45
+ };
46
+ }
47
+ function chainScopedCredentials(...sources) {
48
+ return {
49
+ name: sources.map((source) => source.name).join("+"),
50
+ async get(field) {
51
+ let last;
52
+ for (const source of sources) {
53
+ try {
54
+ return await source.get(field);
55
+ } catch (error) {
56
+ last = error;
57
+ }
58
+ }
59
+ throw last instanceof Error ? last : new ProviderError("CREDENTIAL_MISSING", `No scoped source held field "${field}".`);
60
+ }
61
+ };
62
+ }
41
63
  function staticConfig(config) {
64
+ const providers = new Map(config.providers.map((provider) => [provider.instanceKey, provider]));
65
+ if (providers.size !== config.providers.length) {
66
+ throw new ProviderError("CONFIG_DUPLICATE_INSTANCE", "Provider instance keys must be unique.");
67
+ }
42
68
  const health = new Map;
43
69
  return {
44
70
  name: "static",
45
71
  async provider(instanceKey) {
46
- const provider = config.providers[instanceKey];
47
- return provider ? { instanceKey, ...provider, config: { ...provider.config } } : undefined;
72
+ const provider = providers.get(instanceKey);
73
+ return provider ? { ...provider, config: { ...provider.config } } : undefined;
48
74
  },
49
75
  async list(serviceKey, methodKey) {
50
76
  return (config.services[serviceKey]?.[methodKey] ?? []).map((attachment) => ({
@@ -201,7 +227,78 @@ function createRegistry(options) {
201
227
  }
202
228
  return { call, service };
203
229
  }
204
- var VERSION = "0.1.10";
230
+ function createService(definition, methods, options = {}) {
231
+ const providers = new Map;
232
+ const credentials = new Map;
233
+ const instances = [];
234
+ const serviceMethods = {};
235
+ for (const methodKey of Object.keys(definition.methods)) {
236
+ const attachments = methods[methodKey];
237
+ if (!Array.isArray(attachments)) {
238
+ throw new ProviderError("CONFIG_METHOD_MISSING", `Service method "${definition.key}.${methodKey}" needs an attachment array.`);
239
+ }
240
+ const priorities = new Set;
241
+ serviceMethods[methodKey] = attachments.map((attachment, index) => {
242
+ if (!Number.isInteger(attachment.priority) || attachment.priority < 1 || attachment.priority > 1000) {
243
+ throw new ProviderError("CONFIG_PRIORITY", `Priority for "${definition.key}.${methodKey}" must be an integer from 1 to 1000.`);
244
+ }
245
+ if (priorities.has(attachment.priority)) {
246
+ throw new ProviderError("CONFIG_PRIORITY_DUPLICATE", `Priorities for "${definition.key}.${methodKey}" must be unique.`);
247
+ }
248
+ priorities.add(attachment.priority);
249
+ if (!attachment.provider.methods[attachment.method]) {
250
+ throw new ProviderError("CONFIG_METHOD_UNSUPPORTED", `Provider "${attachment.provider.id}" does not support "${attachment.method}".`);
251
+ }
252
+ const existing = providers.get(attachment.provider.id);
253
+ if (existing && existing !== attachment.provider) {
254
+ throw new ProviderError("CONFIG_PROVIDER_DUPLICATE", `Provider id "${attachment.provider.id}" has more than one definition.`);
255
+ }
256
+ providers.set(attachment.provider.id, attachment.provider);
257
+ const internalKey = `${definition.key}:${methodKey}:${index}`;
258
+ credentials.set(internalKey, attachment.credentials);
259
+ instances.push({
260
+ instanceKey: internalKey,
261
+ providerId: attachment.provider.id,
262
+ enabled: attachment.enabled ?? true,
263
+ config: { ...attachment.config ?? {} },
264
+ secretRef: internalKey
265
+ });
266
+ return {
267
+ instanceKey: internalKey,
268
+ providerMethod: attachment.method,
269
+ priority: attachment.priority,
270
+ enabled: attachment.enabled ?? true
271
+ };
272
+ });
273
+ }
274
+ const registry = createRegistry({
275
+ providers: [...providers.values()],
276
+ config: staticConfig({ providers: instances, services: { [definition.key]: serviceMethods } }),
277
+ credentials: {
278
+ name: [...new Set([...credentials.values()].map((source) => source.name))].join("+"),
279
+ async get(reference, field) {
280
+ const source = credentials.get(reference);
281
+ if (!source)
282
+ throw new ProviderError("CREDENTIAL_SOURCE_MISSING", "The provider credential source is unavailable.");
283
+ return source.get(field);
284
+ }
285
+ },
286
+ ...options
287
+ });
288
+ const dynamic = registry.service(definition);
289
+ return {
290
+ async call(method, args, callOptions) {
291
+ const result = await dynamic.call(method, args, callOptions);
292
+ const { instance: _instance, selected, attempts, ...rest } = result;
293
+ return {
294
+ ...rest,
295
+ ...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod } } : {},
296
+ attempts: attempts.map(({ instanceKey: _instanceKey, ...attempt }) => attempt)
297
+ };
298
+ }
299
+ };
300
+ }
301
+ var VERSION = "0.1.11";
205
302
 
206
303
  // src/email.ts
207
304
  var recipients = (to) => Array.isArray(to) ? [...to] : [to];
package/dist/http.js CHANGED
@@ -38,13 +38,39 @@ function chainCredentials(...sources) {
38
38
  }
39
39
  };
40
40
  }
41
+ function scopeCredentials(source, reference) {
42
+ return {
43
+ name: `${source.name}:${reference}`,
44
+ get: (field) => source.get(reference, field)
45
+ };
46
+ }
47
+ function chainScopedCredentials(...sources) {
48
+ return {
49
+ name: sources.map((source) => source.name).join("+"),
50
+ async get(field) {
51
+ let last;
52
+ for (const source of sources) {
53
+ try {
54
+ return await source.get(field);
55
+ } catch (error) {
56
+ last = error;
57
+ }
58
+ }
59
+ throw last instanceof Error ? last : new ProviderError("CREDENTIAL_MISSING", `No scoped source held field "${field}".`);
60
+ }
61
+ };
62
+ }
41
63
  function staticConfig(config) {
64
+ const providers = new Map(config.providers.map((provider) => [provider.instanceKey, provider]));
65
+ if (providers.size !== config.providers.length) {
66
+ throw new ProviderError("CONFIG_DUPLICATE_INSTANCE", "Provider instance keys must be unique.");
67
+ }
42
68
  const health = new Map;
43
69
  return {
44
70
  name: "static",
45
71
  async provider(instanceKey) {
46
- const provider = config.providers[instanceKey];
47
- return provider ? { instanceKey, ...provider, config: { ...provider.config } } : undefined;
72
+ const provider = providers.get(instanceKey);
73
+ return provider ? { ...provider, config: { ...provider.config } } : undefined;
48
74
  },
49
75
  async list(serviceKey, methodKey) {
50
76
  return (config.services[serviceKey]?.[methodKey] ?? []).map((attachment) => ({
@@ -201,7 +227,78 @@ function createRegistry(options) {
201
227
  }
202
228
  return { call, service };
203
229
  }
204
- var VERSION = "0.1.10";
230
+ function createService(definition, methods, options = {}) {
231
+ const providers = new Map;
232
+ const credentials = new Map;
233
+ const instances = [];
234
+ const serviceMethods = {};
235
+ for (const methodKey of Object.keys(definition.methods)) {
236
+ const attachments = methods[methodKey];
237
+ if (!Array.isArray(attachments)) {
238
+ throw new ProviderError("CONFIG_METHOD_MISSING", `Service method "${definition.key}.${methodKey}" needs an attachment array.`);
239
+ }
240
+ const priorities = new Set;
241
+ serviceMethods[methodKey] = attachments.map((attachment, index) => {
242
+ if (!Number.isInteger(attachment.priority) || attachment.priority < 1 || attachment.priority > 1000) {
243
+ throw new ProviderError("CONFIG_PRIORITY", `Priority for "${definition.key}.${methodKey}" must be an integer from 1 to 1000.`);
244
+ }
245
+ if (priorities.has(attachment.priority)) {
246
+ throw new ProviderError("CONFIG_PRIORITY_DUPLICATE", `Priorities for "${definition.key}.${methodKey}" must be unique.`);
247
+ }
248
+ priorities.add(attachment.priority);
249
+ if (!attachment.provider.methods[attachment.method]) {
250
+ throw new ProviderError("CONFIG_METHOD_UNSUPPORTED", `Provider "${attachment.provider.id}" does not support "${attachment.method}".`);
251
+ }
252
+ const existing = providers.get(attachment.provider.id);
253
+ if (existing && existing !== attachment.provider) {
254
+ throw new ProviderError("CONFIG_PROVIDER_DUPLICATE", `Provider id "${attachment.provider.id}" has more than one definition.`);
255
+ }
256
+ providers.set(attachment.provider.id, attachment.provider);
257
+ const internalKey = `${definition.key}:${methodKey}:${index}`;
258
+ credentials.set(internalKey, attachment.credentials);
259
+ instances.push({
260
+ instanceKey: internalKey,
261
+ providerId: attachment.provider.id,
262
+ enabled: attachment.enabled ?? true,
263
+ config: { ...attachment.config ?? {} },
264
+ secretRef: internalKey
265
+ });
266
+ return {
267
+ instanceKey: internalKey,
268
+ providerMethod: attachment.method,
269
+ priority: attachment.priority,
270
+ enabled: attachment.enabled ?? true
271
+ };
272
+ });
273
+ }
274
+ const registry = createRegistry({
275
+ providers: [...providers.values()],
276
+ config: staticConfig({ providers: instances, services: { [definition.key]: serviceMethods } }),
277
+ credentials: {
278
+ name: [...new Set([...credentials.values()].map((source) => source.name))].join("+"),
279
+ async get(reference, field) {
280
+ const source = credentials.get(reference);
281
+ if (!source)
282
+ throw new ProviderError("CREDENTIAL_SOURCE_MISSING", "The provider credential source is unavailable.");
283
+ return source.get(field);
284
+ }
285
+ },
286
+ ...options
287
+ });
288
+ const dynamic = registry.service(definition);
289
+ return {
290
+ async call(method, args, callOptions) {
291
+ const result = await dynamic.call(method, args, callOptions);
292
+ const { instance: _instance, selected, attempts, ...rest } = result;
293
+ return {
294
+ ...rest,
295
+ ...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod } } : {},
296
+ attempts: attempts.map(({ instanceKey: _instanceKey, ...attempt }) => attempt)
297
+ };
298
+ }
299
+ };
300
+ }
301
+ var VERSION = "0.1.11";
205
302
 
206
303
  // src/http.ts
207
304
  class BudgetExhausted extends ProviderError {
package/dist/index.d.ts CHANGED
@@ -39,6 +39,11 @@ export interface CredentialSource {
39
39
  readonly name: string;
40
40
  get(reference: string, field: string): Promise<string>;
41
41
  }
42
+ /** Credentials already scoped to one provider identity. */
43
+ export interface ScopedCredentialSource {
44
+ readonly name: string;
45
+ get(field: string): Promise<string>;
46
+ }
42
47
  /** Environment variables. `smtp` + `password` → `SMTP_PASSWORD`. */
43
48
  export declare function envCredentials(env: Record<string, string | undefined>): CredentialSource;
44
49
  /**
@@ -50,6 +55,10 @@ export declare function envCredentials(env: Record<string, string | undefined>):
50
55
  * a test that locks the vault and asserts mail still sends.
51
56
  */
52
57
  export declare function chainCredentials(...sources: readonly CredentialSource[]): CredentialSource;
58
+ /** Bind a reference once so a provider receives only its own credential fields. */
59
+ export declare function scopeCredentials(source: CredentialSource, reference: string): ScopedCredentialSource;
60
+ /** Try provider-scoped sources in order, for example Vault then systemd. */
61
+ export declare function chainScopedCredentials(...sources: readonly ScopedCredentialSource[]): ScopedCredentialSource;
53
62
  export interface ProviderHealth {
54
63
  strikes: number;
55
64
  status: 'ok' | 'degraded' | 'offline';
@@ -84,7 +93,7 @@ export interface ConfigSource {
84
93
  recordHealth(serviceKey: string, methodKey: string, instanceKey: string, providerMethod: string, health: ProviderHealth): Promise<void>;
85
94
  }
86
95
  export interface StaticRegistryConfig {
87
- providers: Record<string, Omit<ProviderInstanceConfig, 'instanceKey'>>;
96
+ providers: readonly ProviderInstanceConfig[];
88
97
  services: Record<string, Record<string, readonly ServiceMethodAttachment[]>>;
89
98
  }
90
99
  export declare function staticConfig(config: StaticRegistryConfig): ConfigSource;
@@ -179,6 +188,34 @@ export interface ServiceDefinition<Key extends string, Methods extends ServiceMe
179
188
  export declare function defineService<Key extends string, Methods extends ServiceMethods>(definition: ServiceDefinition<Key, Methods>): ServiceDefinition<Key, Methods>;
180
189
  type ServiceArgs<T> = T extends ServiceMethodContract<infer Args, unknown> ? Args : never;
181
190
  type ServiceResult<T> = T extends ServiceMethodContract<unknown, infer Result> ? Result : never;
191
+ /**
192
+ * One direct service attachment. This is the normal application API: pass the
193
+ * provider itself, its capability, scoped credentials, config and priority.
194
+ * Stable instance keys exist only inside the dynamic control-plane adapter.
195
+ */
196
+ export interface DirectServiceAttachment {
197
+ provider: ProviderDefinition;
198
+ method: string;
199
+ credentials: ScopedCredentialSource;
200
+ config?: Record<string, unknown>;
201
+ priority: number;
202
+ enabled?: boolean;
203
+ }
204
+ export type DirectServiceConfig<Methods extends ServiceMethods> = {
205
+ [Method in keyof Methods]: readonly DirectServiceAttachment[];
206
+ };
207
+ export interface DirectServiceOptions {
208
+ before?: RegistryOptions['before'];
209
+ after?: RegistryOptions['after'];
210
+ }
211
+ export type DirectAttempt = Omit<Attempt, 'instanceKey'>;
212
+ export type DirectCallResult<Result> = Omit<CallResult<Result>, 'instance' | 'selected' | 'attempts'> & {
213
+ selected?: {
214
+ providerId: string;
215
+ providerMethod: string;
216
+ };
217
+ attempts: readonly DirectAttempt[];
218
+ };
182
219
  export interface RegistryOptions {
183
220
  credentials: CredentialSource;
184
221
  config: ConfigSource;
@@ -209,6 +246,16 @@ export declare function createRegistry(options: RegistryOptions): {
209
246
  call<Method extends Extract<keyof Methods, string>>(method: Method, args: ServiceArgs<Methods[Method]>, callOptions?: CallOptions): Promise<CallResult<ServiceResult<Methods[Method]>>>;
210
247
  };
211
248
  };
249
+ /**
250
+ * Create a typed service directly from ordered provider attachments.
251
+ *
252
+ * This deliberately hides provider-instance keys and the ConfigSource
253
+ * ceremony. Applications normally know their providers in code; ForgeZero's
254
+ * dynamic admin control plane can continue to use createRegistry separately.
255
+ */
256
+ export declare function createService<Key extends string, Methods extends ServiceMethods>(definition: ServiceDefinition<Key, Methods>, methods: DirectServiceConfig<Methods>, options?: DirectServiceOptions): {
257
+ call<Method extends Extract<keyof Methods, string>>(method: Method, args: ServiceArgs<Methods[Method]>, callOptions?: CallOptions): Promise<DirectCallResult<ServiceResult<Methods[Method]>>>;
258
+ };
212
259
  /** Passed to `registry.call('email', message)`. */
213
260
  export interface EmailMessage {
214
261
  to: string | readonly string[];
@@ -238,5 +285,5 @@ export interface EmailBatchResult {
238
285
  };
239
286
  results: readonly EmailBatchItemResult[];
240
287
  }
241
- export declare const VERSION = "0.1.10";
288
+ export declare const VERSION = "0.1.11";
242
289
  export {};
package/dist/index.js CHANGED
@@ -38,13 +38,39 @@ function chainCredentials(...sources) {
38
38
  }
39
39
  };
40
40
  }
41
+ function scopeCredentials(source, reference) {
42
+ return {
43
+ name: `${source.name}:${reference}`,
44
+ get: (field) => source.get(reference, field)
45
+ };
46
+ }
47
+ function chainScopedCredentials(...sources) {
48
+ return {
49
+ name: sources.map((source) => source.name).join("+"),
50
+ async get(field) {
51
+ let last;
52
+ for (const source of sources) {
53
+ try {
54
+ return await source.get(field);
55
+ } catch (error) {
56
+ last = error;
57
+ }
58
+ }
59
+ throw last instanceof Error ? last : new ProviderError("CREDENTIAL_MISSING", `No scoped source held field "${field}".`);
60
+ }
61
+ };
62
+ }
41
63
  function staticConfig(config) {
64
+ const providers = new Map(config.providers.map((provider) => [provider.instanceKey, provider]));
65
+ if (providers.size !== config.providers.length) {
66
+ throw new ProviderError("CONFIG_DUPLICATE_INSTANCE", "Provider instance keys must be unique.");
67
+ }
42
68
  const health = new Map;
43
69
  return {
44
70
  name: "static",
45
71
  async provider(instanceKey) {
46
- const provider = config.providers[instanceKey];
47
- return provider ? { instanceKey, ...provider, config: { ...provider.config } } : undefined;
72
+ const provider = providers.get(instanceKey);
73
+ return provider ? { ...provider, config: { ...provider.config } } : undefined;
48
74
  },
49
75
  async list(serviceKey, methodKey) {
50
76
  return (config.services[serviceKey]?.[methodKey] ?? []).map((attachment) => ({
@@ -201,17 +227,91 @@ function createRegistry(options) {
201
227
  }
202
228
  return { call, service };
203
229
  }
204
- var VERSION = "0.1.10";
230
+ function createService(definition, methods, options = {}) {
231
+ const providers = new Map;
232
+ const credentials = new Map;
233
+ const instances = [];
234
+ const serviceMethods = {};
235
+ for (const methodKey of Object.keys(definition.methods)) {
236
+ const attachments = methods[methodKey];
237
+ if (!Array.isArray(attachments)) {
238
+ throw new ProviderError("CONFIG_METHOD_MISSING", `Service method "${definition.key}.${methodKey}" needs an attachment array.`);
239
+ }
240
+ const priorities = new Set;
241
+ serviceMethods[methodKey] = attachments.map((attachment, index) => {
242
+ if (!Number.isInteger(attachment.priority) || attachment.priority < 1 || attachment.priority > 1000) {
243
+ throw new ProviderError("CONFIG_PRIORITY", `Priority for "${definition.key}.${methodKey}" must be an integer from 1 to 1000.`);
244
+ }
245
+ if (priorities.has(attachment.priority)) {
246
+ throw new ProviderError("CONFIG_PRIORITY_DUPLICATE", `Priorities for "${definition.key}.${methodKey}" must be unique.`);
247
+ }
248
+ priorities.add(attachment.priority);
249
+ if (!attachment.provider.methods[attachment.method]) {
250
+ throw new ProviderError("CONFIG_METHOD_UNSUPPORTED", `Provider "${attachment.provider.id}" does not support "${attachment.method}".`);
251
+ }
252
+ const existing = providers.get(attachment.provider.id);
253
+ if (existing && existing !== attachment.provider) {
254
+ throw new ProviderError("CONFIG_PROVIDER_DUPLICATE", `Provider id "${attachment.provider.id}" has more than one definition.`);
255
+ }
256
+ providers.set(attachment.provider.id, attachment.provider);
257
+ const internalKey = `${definition.key}:${methodKey}:${index}`;
258
+ credentials.set(internalKey, attachment.credentials);
259
+ instances.push({
260
+ instanceKey: internalKey,
261
+ providerId: attachment.provider.id,
262
+ enabled: attachment.enabled ?? true,
263
+ config: { ...attachment.config ?? {} },
264
+ secretRef: internalKey
265
+ });
266
+ return {
267
+ instanceKey: internalKey,
268
+ providerMethod: attachment.method,
269
+ priority: attachment.priority,
270
+ enabled: attachment.enabled ?? true
271
+ };
272
+ });
273
+ }
274
+ const registry = createRegistry({
275
+ providers: [...providers.values()],
276
+ config: staticConfig({ providers: instances, services: { [definition.key]: serviceMethods } }),
277
+ credentials: {
278
+ name: [...new Set([...credentials.values()].map((source) => source.name))].join("+"),
279
+ async get(reference, field) {
280
+ const source = credentials.get(reference);
281
+ if (!source)
282
+ throw new ProviderError("CREDENTIAL_SOURCE_MISSING", "The provider credential source is unavailable.");
283
+ return source.get(field);
284
+ }
285
+ },
286
+ ...options
287
+ });
288
+ const dynamic = registry.service(definition);
289
+ return {
290
+ async call(method, args, callOptions) {
291
+ const result = await dynamic.call(method, args, callOptions);
292
+ const { instance: _instance, selected, attempts, ...rest } = result;
293
+ return {
294
+ ...rest,
295
+ ...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod } } : {},
296
+ attempts: attempts.map(({ instanceKey: _instanceKey, ...attempt }) => attempt)
297
+ };
298
+ }
299
+ };
300
+ }
301
+ var VERSION = "0.1.11";
205
302
  export {
206
303
  staticConfig,
207
304
  serviceMethod,
305
+ scopeCredentials,
208
306
  nextHealth,
209
307
  envCredentials,
210
308
  defineSingleMethodProvider,
211
309
  defineService,
212
310
  defineProviderMethod,
213
311
  defineProvider,
312
+ createService,
214
313
  createRegistry,
314
+ chainScopedCredentials,
215
315
  chainCredentials,
216
316
  VERSION,
217
317
  STRIKES_TO_OFFLINE,