@forgezero/providers 0.1.22 → 0.1.23

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 CHANGED
@@ -45,6 +45,7 @@ These are supported consumer entry points, not every internal module shipped for
45
45
  | @forgezero/providers/storage | S3-compatible object storage, SigV4 signed with Web Crypto and no vendor SDK. | portable | [Reference + usage](#forgezero-providers-storage) |
46
46
  | @forgezero/providers/translation | Google AI Studio translation with strict batch alignment and classified quota failures. | portable | [Reference + usage](#forgezero-providers-translation) |
47
47
  | @forgezero/providers/realtime | Cloudflare KV node-directory and Durable Object fan-out clients for project-defined realtime services. | portable | [Reference + usage](#forgezero-providers-realtime) |
48
+ | @forgezero/providers/git | Versioned Git Connect contracts for accounts, repositories, branches, webhooks and short-lived clone credentials; GitHub, GitLab and private forges attach without changing the service. | portable | [Reference + usage](#forgezero-providers-git) |
48
49
 
49
50
  ## Commands
50
51
 
@@ -373,11 +374,40 @@ import {
373
374
  export const selectedCapability = RealtimeProviderError;
374
375
  ```
375
376
 
377
+ <a id="forgezero-providers-git"></a>
378
+ ## @forgezero/providers/git
379
+
380
+ Versioned Git Connect contracts for accounts, repositories, branches, webhooks and short-lived clone credentials; GitHub, GitLab and private forges attach without changing the service. This is a supported entry point. Import only the named values used by the adjacent task example; the declaration file remains the complete API reference.
381
+
382
+ ```text
383
+ import {
384
+ defineGitConnectProvider,
385
+ } from '@forgezero/providers/git';
386
+ ```
387
+
388
+ ## @forgezero/providers/git — Use this entry point
389
+
390
+ This minimal executable use imports one concrete value from this exact entry point without a wildcard or package-root detour. Keep the value or values the application actually needs; the full named inventory remains directly above it.
391
+
392
+ ```text
393
+ import {
394
+ defineGitConnectProvider,
395
+ } from '@forgezero/providers/git';
396
+
397
+ export const selectedCapability = defineGitConnectProvider;
398
+ ```
399
+
376
400
  ## Built-in provider capabilities
377
401
 
378
402
  A provider owns versioned capabilities; it does not own a service. A service method attaches an exact provider, method and version in an ordered array. Current, legacy and deprecated branches can coexist; retired branches never run.
379
403
 
380
404
  ```text
405
+ github-app
406
+ - listAccounts
407
+ - listRepositories
408
+ - listBranches
409
+ - ensureWebhook
410
+ - cloneCredential
381
411
  evm-rpc
382
412
  - request
383
413
  jetemail
@@ -395,6 +425,29 @@ google-ai-studio
395
425
  - translate
396
426
  ```
397
427
 
428
+ ## GitHub App (github-app)
429
+
430
+ Import: @forgezero/providers/git. Each method below is a provider capability that a service attaches by exact version. User OAuth verifies an email and is discarded before the mandatory passkey. App installation credentials are minted on demand, limited to one selected repository, and expire rather than being stored. GitLab or a private forge implements the same Git Connect service without pretending to be GitHub.
431
+
432
+ ```text
433
+ importPath: '@forgezero/providers/git'
434
+ providerId: 'github-app'
435
+ methods:
436
+ - listAccounts
437
+ - listRepositories
438
+ - listBranches
439
+ - ensureWebhook
440
+ - cloneCredential
441
+ credentials:
442
+ - clientSecret
443
+ - privateKey
444
+ - webhookSecret
445
+ config:
446
+ - clientId
447
+ - appId
448
+ - slug
449
+ ```
450
+
398
451
  ## EVM JSON-RPC node (evm-rpc)
399
452
 
400
453
  Import: @forgezero/providers/chain. Each method below is a provider capability that a service attaches by exact version. Several endpoints per chain is the normal case, not a luxury: public RPC is rate-limited and paid gateways have outages, and a scanner that stops when one is down stops crediting deposits while the money sits on the chain. "Already known" and "nonce too low" are TERMINAL — the transaction is already in flight, so rebroadcasting through another node is how a sweep gets sent twice.
package/dist/binance.js CHANGED
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
 
341
341
  // src/http.ts
342
342
  class BudgetExhausted extends ProviderError {
package/dist/chain.js CHANGED
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
 
341
341
  // src/chain.ts
342
342
  var hexToNumber = (value) => Number(BigInt(value));
package/dist/database.js CHANGED
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
 
341
341
  // src/database.ts
342
342
  var pools = new Map;
package/dist/email.js CHANGED
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
 
341
341
  // src/email.ts
342
342
  var recipients = (to) => Array.isArray(to) ? [...to] : [to];
package/dist/git.d.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { type FailureKind, type InvokeContext, type ProviderDefinition } from './index';
2
+ export interface GitAccount {
3
+ id: string;
4
+ label: string;
5
+ kind: 'user' | 'organization' | 'group' | 'other';
6
+ }
7
+ export interface GitRepository {
8
+ id: string;
9
+ fullName: string;
10
+ private: boolean;
11
+ defaultBranch: string;
12
+ cloneUrl: string;
13
+ }
14
+ export interface GitBranch {
15
+ name: string;
16
+ protected?: boolean;
17
+ }
18
+ export interface GitCloneCredential {
19
+ username: string;
20
+ token: string;
21
+ expiresAtTs: number;
22
+ }
23
+ export interface GitAccountRequest {
24
+ cursor?: string;
25
+ }
26
+ export interface GitRepositoryRequest {
27
+ accountId: string;
28
+ cursor?: string;
29
+ }
30
+ export interface GitBranchRequest {
31
+ accountId: string;
32
+ repository: string;
33
+ cursor?: string;
34
+ }
35
+ export interface GitWebhookRequest {
36
+ accountId: string;
37
+ repository: string;
38
+ callbackUrl: string;
39
+ secret: string;
40
+ events: readonly ['push'];
41
+ }
42
+ export interface GitCloneCredentialRequest {
43
+ accountId: string;
44
+ repository: string;
45
+ }
46
+ export declare const githubAppConfigSchema: {
47
+ readonly type: "object";
48
+ readonly properties: {
49
+ readonly clientId: {
50
+ readonly type: "string";
51
+ readonly minLength: 10;
52
+ readonly maxLength: 128;
53
+ };
54
+ readonly appId: {
55
+ readonly type: "string";
56
+ readonly pattern: "^[1-9][0-9]{0,19}$";
57
+ };
58
+ readonly slug: {
59
+ readonly type: "string";
60
+ readonly pattern: "^[a-z0-9][a-z0-9-]{0,99}$";
61
+ };
62
+ };
63
+ readonly required: readonly ["clientId", "appId", "slug"];
64
+ readonly additionalProperties: false;
65
+ };
66
+ export declare const githubAppCredentialSchema: {
67
+ readonly type: "object";
68
+ readonly properties: {
69
+ readonly clientSecret: {
70
+ readonly type: "string";
71
+ readonly minLength: 20;
72
+ readonly maxLength: 512;
73
+ readonly writeOnly: true;
74
+ };
75
+ readonly privateKey: {
76
+ readonly type: "string";
77
+ readonly minLength: 800;
78
+ readonly maxLength: 32768;
79
+ readonly writeOnly: true;
80
+ };
81
+ readonly webhookSecret: {
82
+ readonly type: "string";
83
+ readonly minLength: 32;
84
+ readonly maxLength: 256;
85
+ readonly writeOnly: true;
86
+ };
87
+ };
88
+ readonly required: readonly ["clientSecret", "privateKey", "webhookSecret"];
89
+ readonly additionalProperties: false;
90
+ };
91
+ export declare const gitConnectService: import("./index").ServiceDefinition<"git-connect", {
92
+ listAccounts: import("./index").ServiceMethodContract<GitAccountRequest, readonly GitAccount[]>;
93
+ listRepositories: import("./index").ServiceMethodContract<GitRepositoryRequest, readonly GitRepository[]>;
94
+ listBranches: import("./index").ServiceMethodContract<GitBranchRequest, readonly GitBranch[]>;
95
+ ensureWebhook: import("./index").ServiceMethodContract<GitWebhookRequest, {
96
+ webhookId: string;
97
+ created: boolean;
98
+ }>;
99
+ cloneCredential: import("./index").ServiceMethodContract<GitCloneCredentialRequest, GitCloneCredential>;
100
+ }>;
101
+ export interface GitConnectAdapter {
102
+ listAccounts(context: InvokeContext, args: GitAccountRequest): Promise<readonly GitAccount[]>;
103
+ listRepositories(context: InvokeContext, args: GitRepositoryRequest): Promise<readonly GitRepository[]>;
104
+ listBranches(context: InvokeContext, args: GitBranchRequest): Promise<readonly GitBranch[]>;
105
+ ensureWebhook(context: InvokeContext, args: GitWebhookRequest): Promise<{
106
+ webhookId: string;
107
+ created: boolean;
108
+ }>;
109
+ cloneCredential(context: InvokeContext, args: GitCloneCredentialRequest): Promise<GitCloneCredential>;
110
+ }
111
+ /**
112
+ * Define one Git forge without coupling it to ForgeZero Vault or API storage.
113
+ * A GitHub App, GitLab OAuth application, self-hosted forge, or future provider
114
+ * supplies this adapter; the service keeps the same five typed operations.
115
+ */
116
+ export declare function defineGitConnectProvider(args: {
117
+ id: string;
118
+ label: string;
119
+ version: string;
120
+ adapter: GitConnectAdapter;
121
+ credentials?: Record<string, unknown>;
122
+ config?: Record<string, unknown>;
123
+ classify?: (error: unknown) => FailureKind;
124
+ }): ProviderDefinition;
package/dist/git.js ADDED
@@ -0,0 +1,400 @@
1
+ // src/index.ts
2
+ class ProviderError extends Error {
3
+ code;
4
+ details;
5
+ constructor(code, message, details) {
6
+ super(message);
7
+ this.code = code;
8
+ this.details = details;
9
+ this.name = "ProviderError";
10
+ }
11
+ }
12
+ function envCredentials(env) {
13
+ return {
14
+ name: "env",
15
+ async get(reference, field) {
16
+ const key = `${reference}_${field}`.replace(/[.-]/g, "_").toUpperCase();
17
+ const value = env[key];
18
+ if (value === undefined) {
19
+ throw new ProviderError("CREDENTIAL_MISSING", `Set ${key} in the environment.`);
20
+ }
21
+ return value;
22
+ }
23
+ };
24
+ }
25
+ function chainCredentials(...sources) {
26
+ return {
27
+ name: sources.map((source) => source.name).join("+"),
28
+ async get(reference, field) {
29
+ let last;
30
+ for (const source of sources) {
31
+ try {
32
+ return await source.get(reference, field);
33
+ } catch (error) {
34
+ last = error;
35
+ }
36
+ }
37
+ throw last instanceof Error ? last : new ProviderError("CREDENTIAL_MISSING", `No source held ${reference}.${field}.`);
38
+ }
39
+ };
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
+ }
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
+ }
68
+ const health = new Map;
69
+ return {
70
+ name: "static",
71
+ async provider(instanceKey) {
72
+ const provider = providers.get(instanceKey);
73
+ return provider ? { ...provider, config: { ...provider.config } } : undefined;
74
+ },
75
+ async list(serviceKey, methodKey) {
76
+ return (config.services[serviceKey]?.[methodKey] ?? []).map((attachment) => ({
77
+ ...attachment,
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
+ }));
80
+ },
81
+ async recordHealth(serviceKey, methodKey, instanceKey, providerMethod, providerMethodVersion, next) {
82
+ health.set(`${serviceKey}:${methodKey}:${instanceKey}:${providerMethod}:${providerMethodVersion}`, next);
83
+ }
84
+ };
85
+ }
86
+ function defineProviderMethod(method) {
87
+ return method;
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
+ };
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
+ }
120
+ return spec;
121
+ }
122
+ function defineSingleMethodProvider(spec) {
123
+ const { method, version = "v1", lifecycle = "current", invoke, classify, ...identity } = spec;
124
+ return defineProvider({
125
+ ...identity,
126
+ methods: { [method]: defineProviderMethod({ version, lifecycle, invoke, classify }) }
127
+ });
128
+ }
129
+ var STRIKES_TO_OFFLINE = 3;
130
+ function serviceMethod() {
131
+ return Object.freeze({});
132
+ }
133
+ function defineService(definition) {
134
+ return definition;
135
+ }
136
+ function nextHealth(current, kind) {
137
+ if (kind === "success")
138
+ return { strikes: 0, status: "ok" };
139
+ if (kind === "backoff")
140
+ return current ?? { strikes: 0, status: "ok" };
141
+ const strikes = (current?.strikes ?? 0) + 1;
142
+ return {
143
+ strikes,
144
+ status: strikes >= STRIKES_TO_OFFLINE ? "offline" : "degraded",
145
+ lastFailureAtTs: Date.now()
146
+ };
147
+ }
148
+ function createRegistry(options) {
149
+ const byId = new Map(options.providers.map((provider) => [provider.id, provider]));
150
+ async function call(serviceKey, methodKey, args, callOptions = {}) {
151
+ const configured = [...await options.config.list(serviceKey, methodKey)].filter((attachment) => attachment.enabled).sort((a, b) => a.priority - b.priority);
152
+ const attempts = [];
153
+ for (const attachment of configured) {
154
+ const requestedVersion = attachment.providerMethodVersion;
155
+ if (callOptions.signal?.aborted) {
156
+ const cancelled = {
157
+ ok: false,
158
+ fallbackUsed: attempts.length > 0,
159
+ attempts,
160
+ error: new ProviderError("CALL_ABORTED", `The "${serviceKey}.${methodKey}" call was cancelled.`)
161
+ };
162
+ await options.after?.(cancelled);
163
+ return cancelled;
164
+ }
165
+ const instance = await options.config.provider(attachment.instanceKey);
166
+ if (!instance) {
167
+ attempts.push({ providerId: "unknown", instanceKey: attachment.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: requestedVersion ?? "current", outcome: "skipped", error: "instance not registered" });
168
+ continue;
169
+ }
170
+ const spec = byId.get(instance.providerId);
171
+ const method = methodBranch(spec, attachment.providerMethod, requestedVersion);
172
+ if (!spec || !method) {
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" });
178
+ continue;
179
+ }
180
+ if (!instance.enabled) {
181
+ attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "instance disabled" });
182
+ continue;
183
+ }
184
+ if (attachment.health?.status === "offline") {
185
+ attempts.push({ providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version, outcome: "skipped", error: "offline" });
186
+ continue;
187
+ }
188
+ await options.before?.({ service: serviceKey, method: methodKey, provider: instance.providerId, instance: instance.instanceKey });
189
+ const startedAt = performance.now();
190
+ try {
191
+ const result = await method.invoke({
192
+ config: instance.config,
193
+ secret: (field) => options.credentials.get(instance.secretRef, field),
194
+ signal: callOptions.signal
195
+ }, args);
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"));
198
+ const sent = {
199
+ ok: true,
200
+ result,
201
+ provider: instance.providerId,
202
+ instance: instance.instanceKey,
203
+ method: attachment.providerMethod,
204
+ selected: { providerId: instance.providerId, instanceKey: instance.instanceKey, providerMethod: attachment.providerMethod, providerMethodVersion: method.version },
205
+ fallbackUsed: attempts.length > 1,
206
+ attempts
207
+ };
208
+ await options.after?.(sent);
209
+ return sent;
210
+ } catch (error) {
211
+ if (callOptions.signal?.aborted) {
212
+ const cancelled = {
213
+ ok: false,
214
+ fallbackUsed: attempts.length > 0,
215
+ attempts,
216
+ error: new ProviderError("CALL_ABORTED", `The "${serviceKey}.${methodKey}" call was cancelled.`)
217
+ };
218
+ await options.after?.(cancelled);
219
+ return cancelled;
220
+ }
221
+ const kind = method.classify(error);
222
+ const message = error instanceof Error ? error.message : String(error);
223
+ const code = error instanceof ProviderError ? error.code : typeof error?.code === "string" ? error.code : undefined;
224
+ attempts.push({
225
+ providerId: instance.providerId,
226
+ instanceKey: instance.instanceKey,
227
+ providerMethod: attachment.providerMethod,
228
+ providerMethodVersion: method.version,
229
+ outcome: "failed",
230
+ kind,
231
+ durationMs: performance.now() - startedAt,
232
+ failure: { kind, ...code ? { code } : {}, message },
233
+ error: message
234
+ });
235
+ await options.config.recordHealth(serviceKey, methodKey, instance.instanceKey, attachment.providerMethod, method.version, nextHealth(attachment.health, kind));
236
+ if (kind === "terminal") {
237
+ const refused = {
238
+ ok: false,
239
+ fallbackUsed: attempts.length > 1,
240
+ attempts,
241
+ error: new ProviderError("PAYLOAD_REJECTED", "The request was refused as malformed; no provider will accept it.")
242
+ };
243
+ await options.after?.(refused);
244
+ return refused;
245
+ }
246
+ }
247
+ }
248
+ const failed = {
249
+ ok: false,
250
+ fallbackUsed: attempts.length > 1,
251
+ attempts,
252
+ error: new ProviderError(attempts.length === 0 ? "NO_PROVIDER" : "ALL_PROVIDERS_FAILED", attempts.length === 0 ? `No provider method is configured for "${serviceKey}.${methodKey}".` : `Every provider method for "${serviceKey}.${methodKey}" failed or was skipped.`)
253
+ };
254
+ await options.after?.(failed);
255
+ return failed;
256
+ }
257
+ function service(definition) {
258
+ return {
259
+ call(method, args, callOptions) {
260
+ return call(definition.key, method, args, callOptions);
261
+ }
262
+ };
263
+ }
264
+ return { call, service };
265
+ }
266
+ function createService(definition, methods, options = {}) {
267
+ const providers = new Map;
268
+ const credentials = new Map;
269
+ const instances = [];
270
+ const serviceMethods = {};
271
+ for (const methodKey of Object.keys(definition.methods)) {
272
+ const attachments = methods[methodKey];
273
+ if (!Array.isArray(attachments)) {
274
+ throw new ProviderError("CONFIG_METHOD_MISSING", `Service method "${definition.key}.${methodKey}" needs an attachment array.`);
275
+ }
276
+ const priorities = new Set;
277
+ serviceMethods[methodKey] = attachments.map((attachment, index) => {
278
+ if (!Number.isInteger(attachment.priority) || attachment.priority < 1 || attachment.priority > 1000) {
279
+ throw new ProviderError("CONFIG_PRIORITY", `Priority for "${definition.key}.${methodKey}" must be an integer from 1 to 1000.`);
280
+ }
281
+ if (priorities.has(attachment.priority)) {
282
+ throw new ProviderError("CONFIG_PRIORITY_DUPLICATE", `Priorities for "${definition.key}.${methodKey}" must be unique.`);
283
+ }
284
+ priorities.add(attachment.priority);
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"}".`);
288
+ }
289
+ const existing = providers.get(attachment.provider.id);
290
+ if (existing && existing !== attachment.provider) {
291
+ throw new ProviderError("CONFIG_PROVIDER_DUPLICATE", `Provider id "${attachment.provider.id}" has more than one definition.`);
292
+ }
293
+ providers.set(attachment.provider.id, attachment.provider);
294
+ const internalKey = `${definition.key}:${methodKey}:${index}`;
295
+ credentials.set(internalKey, attachment.credentials);
296
+ instances.push({
297
+ instanceKey: internalKey,
298
+ providerId: attachment.provider.id,
299
+ enabled: attachment.enabled ?? true,
300
+ config: { ...attachment.config ?? {} },
301
+ secretRef: internalKey
302
+ });
303
+ return {
304
+ instanceKey: internalKey,
305
+ providerMethod: attachment.method,
306
+ providerMethodVersion: branch.version,
307
+ priority: attachment.priority,
308
+ enabled: attachment.enabled ?? true
309
+ };
310
+ });
311
+ }
312
+ const registry = createRegistry({
313
+ providers: [...providers.values()],
314
+ config: staticConfig({ providers: instances, services: { [definition.key]: serviceMethods } }),
315
+ credentials: {
316
+ name: [...new Set([...credentials.values()].map((source) => source.name))].join("+"),
317
+ async get(reference, field) {
318
+ const source = credentials.get(reference);
319
+ if (!source)
320
+ throw new ProviderError("CREDENTIAL_SOURCE_MISSING", "The provider credential source is unavailable.");
321
+ return source.get(field);
322
+ }
323
+ },
324
+ ...options
325
+ });
326
+ const dynamic = registry.service(definition);
327
+ return {
328
+ async call(method, args, callOptions) {
329
+ const result = await dynamic.call(method, args, callOptions);
330
+ const { instance: _instance, selected, attempts, ...rest } = result;
331
+ return {
332
+ ...rest,
333
+ ...selected ? { selected: { providerId: selected.providerId, providerMethod: selected.providerMethod, providerMethodVersion: selected.providerMethodVersion } } : {},
334
+ attempts: attempts.map(({ instanceKey: _instanceKey, ...attempt }) => attempt)
335
+ };
336
+ }
337
+ };
338
+ }
339
+ var VERSION = "0.1.23";
340
+
341
+ // src/git.ts
342
+ var githubAppConfigSchema = {
343
+ type: "object",
344
+ properties: {
345
+ clientId: { type: "string", minLength: 10, maxLength: 128 },
346
+ appId: { type: "string", pattern: "^[1-9][0-9]{0,19}$" },
347
+ slug: { type: "string", pattern: "^[a-z0-9][a-z0-9-]{0,99}$" }
348
+ },
349
+ required: ["clientId", "appId", "slug"],
350
+ additionalProperties: false
351
+ };
352
+ var githubAppCredentialSchema = {
353
+ type: "object",
354
+ properties: {
355
+ clientSecret: { type: "string", minLength: 20, maxLength: 512, writeOnly: true },
356
+ privateKey: { type: "string", minLength: 800, maxLength: 32768, writeOnly: true },
357
+ webhookSecret: { type: "string", minLength: 32, maxLength: 256, writeOnly: true }
358
+ },
359
+ required: ["clientSecret", "privateKey", "webhookSecret"],
360
+ additionalProperties: false
361
+ };
362
+ var gitConnectService = defineService({
363
+ key: "git-connect",
364
+ methods: {
365
+ listAccounts: serviceMethod(),
366
+ listRepositories: serviceMethod(),
367
+ listBranches: serviceMethod(),
368
+ ensureWebhook: serviceMethod(),
369
+ cloneCredential: serviceMethod()
370
+ }
371
+ });
372
+ function defineGitConnectProvider(args) {
373
+ const classify = args.classify ?? (() => "retryable");
374
+ const branch = (invoke) => defineProviderMethodBranches({
375
+ currentVersion: args.version,
376
+ versions: {
377
+ [args.version]: { version: args.version, lifecycle: "current", invoke, classify }
378
+ }
379
+ });
380
+ return defineProvider({
381
+ id: args.id,
382
+ label: args.label,
383
+ multiInstance: true,
384
+ credentials: args.credentials,
385
+ config: args.config,
386
+ methods: {
387
+ listAccounts: branch(args.adapter.listAccounts),
388
+ listRepositories: branch(args.adapter.listRepositories),
389
+ listBranches: branch(args.adapter.listBranches),
390
+ ensureWebhook: branch(args.adapter.ensureWebhook),
391
+ cloneCredential: branch(args.adapter.cloneCredential)
392
+ }
393
+ });
394
+ }
395
+ export {
396
+ githubAppCredentialSchema,
397
+ githubAppConfigSchema,
398
+ gitConnectService,
399
+ defineGitConnectProvider
400
+ };
package/dist/http.js CHANGED
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
 
341
341
  // src/http.ts
342
342
  class BudgetExhausted extends ProviderError {
package/dist/index.d.ts CHANGED
@@ -303,5 +303,5 @@ export interface EmailBatchResult {
303
303
  };
304
304
  results: readonly EmailBatchItemResult[];
305
305
  }
306
- export declare const VERSION = "0.1.22";
306
+ export declare const VERSION = "0.1.23";
307
307
  export {};
package/dist/index.js CHANGED
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
  export {
341
341
  staticConfig,
342
342
  serviceMethod,
package/dist/pool.js CHANGED
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
 
341
341
  // src/http.ts
342
342
  class BudgetExhausted extends ProviderError {
package/dist/storage.js CHANGED
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
 
341
341
  // src/storage.ts
342
342
  var encoder = new TextEncoder;
@@ -336,7 +336,7 @@ function createService(definition, methods, options = {}) {
336
336
  }
337
337
  };
338
338
  }
339
- var VERSION = "0.1.22";
339
+ var VERSION = "0.1.23";
340
340
 
341
341
  // src/translation.ts
342
342
  var GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/providers",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -46,6 +46,10 @@
46
46
  "./realtime": {
47
47
  "types": "./dist/realtime.d.ts",
48
48
  "default": "./dist/realtime.js"
49
+ },
50
+ "./git": {
51
+ "types": "./dist/git.d.ts",
52
+ "default": "./dist/git.js"
49
53
  }
50
54
  },
51
55
  "scripts": {