@forgezero/providers 0.1.0
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/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/binance.d.ts +27 -0
- package/dist/binance.js +493 -0
- package/dist/chain.d.ts +99 -0
- package/dist/chain.js +279 -0
- package/dist/database.d.ts +49 -0
- package/dist/database.js +209 -0
- package/dist/email.d.ts +68 -0
- package/dist/email.js +278 -0
- package/dist/http.d.ts +100 -0
- package/dist/http.js +283 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.js +139 -0
- package/dist/pool.d.ts +112 -0
- package/dist/pool.js +376 -0
- package/dist/storage.d.ts +137 -0
- package/dist/storage.js +441 -0
- package/package.json +82 -0
package/dist/storage.js
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
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 staticConfig(services) {
|
|
42
|
+
const health = new Map;
|
|
43
|
+
return {
|
|
44
|
+
name: "static",
|
|
45
|
+
async list(serviceKey) {
|
|
46
|
+
return (services[serviceKey] ?? []).map((provider) => ({
|
|
47
|
+
...provider,
|
|
48
|
+
health: health.get(`${serviceKey}:${provider.providerId}`) ?? provider.health
|
|
49
|
+
}));
|
|
50
|
+
},
|
|
51
|
+
async recordHealth(serviceKey, providerId, next) {
|
|
52
|
+
health.set(`${serviceKey}:${providerId}`, next);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function defineProvider(spec) {
|
|
57
|
+
return spec;
|
|
58
|
+
}
|
|
59
|
+
var STRIKES_TO_OFFLINE = 3;
|
|
60
|
+
function nextHealth(current, kind) {
|
|
61
|
+
if (kind === "success")
|
|
62
|
+
return { strikes: 0, status: "ok" };
|
|
63
|
+
if (kind === "backoff")
|
|
64
|
+
return current ?? { strikes: 0, status: "ok" };
|
|
65
|
+
const strikes = (current?.strikes ?? 0) + 1;
|
|
66
|
+
return {
|
|
67
|
+
strikes,
|
|
68
|
+
status: strikes >= STRIKES_TO_OFFLINE ? "offline" : "degraded",
|
|
69
|
+
lastFailureAtTs: Date.now()
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function createRegistry(options) {
|
|
73
|
+
const byId = new Map(options.providers.map((provider) => [provider.id, provider]));
|
|
74
|
+
async function call(serviceKey, args) {
|
|
75
|
+
const configured = [...await options.config.list(serviceKey)].filter((provider) => provider.enabled).sort((a, b) => a.priority - b.priority);
|
|
76
|
+
const attempts = [];
|
|
77
|
+
for (const entry of configured) {
|
|
78
|
+
const spec = byId.get(entry.providerId);
|
|
79
|
+
if (!spec) {
|
|
80
|
+
attempts.push({ providerId: entry.providerId, outcome: "skipped", error: "not registered" });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (entry.health?.status === "offline") {
|
|
84
|
+
attempts.push({ providerId: entry.providerId, outcome: "skipped", error: "offline" });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
options.before?.({ service: serviceKey, provider: entry.providerId });
|
|
88
|
+
try {
|
|
89
|
+
const result = await spec.invoke({
|
|
90
|
+
config: entry.config,
|
|
91
|
+
secret: (field) => options.credentials.get(entry.secretRef, field)
|
|
92
|
+
}, args);
|
|
93
|
+
attempts.push({ providerId: entry.providerId, outcome: "sent" });
|
|
94
|
+
await options.config.recordHealth(serviceKey, entry.providerId, nextHealth(entry.health, "success"));
|
|
95
|
+
const sent = { ok: true, result, provider: entry.providerId, attempts };
|
|
96
|
+
options.after?.(sent);
|
|
97
|
+
return sent;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const kind = spec.classify(error);
|
|
100
|
+
attempts.push({
|
|
101
|
+
providerId: entry.providerId,
|
|
102
|
+
outcome: "failed",
|
|
103
|
+
kind,
|
|
104
|
+
error: error instanceof Error ? error.message : String(error)
|
|
105
|
+
});
|
|
106
|
+
await options.config.recordHealth(serviceKey, entry.providerId, nextHealth(entry.health, kind));
|
|
107
|
+
if (kind === "terminal") {
|
|
108
|
+
const refused = {
|
|
109
|
+
ok: false,
|
|
110
|
+
attempts,
|
|
111
|
+
error: new ProviderError("PAYLOAD_REJECTED", "The request was refused as malformed; no provider will accept it.")
|
|
112
|
+
};
|
|
113
|
+
options.after?.(refused);
|
|
114
|
+
return refused;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const failed = {
|
|
119
|
+
ok: false,
|
|
120
|
+
attempts,
|
|
121
|
+
error: new ProviderError(attempts.length === 0 ? "NO_PROVIDER" : "ALL_PROVIDERS_FAILED", attempts.length === 0 ? `No provider is configured for "${serviceKey}".` : `Every provider for "${serviceKey}" failed or was skipped.`)
|
|
122
|
+
};
|
|
123
|
+
options.after?.(failed);
|
|
124
|
+
return failed;
|
|
125
|
+
}
|
|
126
|
+
return { call };
|
|
127
|
+
}
|
|
128
|
+
var VERSION = "0.1.0";
|
|
129
|
+
|
|
130
|
+
// src/storage.ts
|
|
131
|
+
var encoder = new TextEncoder;
|
|
132
|
+
|
|
133
|
+
class StorageError extends ProviderError {
|
|
134
|
+
status;
|
|
135
|
+
constructor(code, message, status) {
|
|
136
|
+
super(code, message);
|
|
137
|
+
this.status = status;
|
|
138
|
+
this.name = "StorageError";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
var MAX_SINGLE_PUT_BYTES = 5 * 1024 * 1024 * 1024;
|
|
142
|
+
var hex = (buffer) => [...new Uint8Array(buffer)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
143
|
+
var sha256Hex = async (data) => hex(await crypto.subtle.digest("SHA-256", typeof data === "string" ? encoder.encode(data) : data));
|
|
144
|
+
async function hmac(key, message) {
|
|
145
|
+
const imported = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, [
|
|
146
|
+
"sign"
|
|
147
|
+
]);
|
|
148
|
+
return new Uint8Array(await crypto.subtle.sign("HMAC", imported, encoder.encode(message)));
|
|
149
|
+
}
|
|
150
|
+
function encodeSegment(segment) {
|
|
151
|
+
return encodeURIComponent(segment).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
152
|
+
}
|
|
153
|
+
var encodeKey = (key) => key.split("/").map(encodeSegment).join("/");
|
|
154
|
+
function amzDate(at) {
|
|
155
|
+
const full = at.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
|
|
156
|
+
return { full, short: full.slice(0, 8) };
|
|
157
|
+
}
|
|
158
|
+
async function signRequest(args) {
|
|
159
|
+
const service = args.service ?? "s3";
|
|
160
|
+
const { full, short } = amzDate(args.at);
|
|
161
|
+
const headers = {
|
|
162
|
+
...args.headers,
|
|
163
|
+
host: args.url.host,
|
|
164
|
+
"x-amz-content-sha256": args.payloadHash,
|
|
165
|
+
"x-amz-date": full,
|
|
166
|
+
...args.credentials.sessionToken ? { "x-amz-security-token": args.credentials.sessionToken } : {}
|
|
167
|
+
};
|
|
168
|
+
const canonicalHeaderNames = Object.keys(headers).map((name) => name.toLowerCase()).sort();
|
|
169
|
+
const lower = Object.fromEntries(Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value]));
|
|
170
|
+
const canonicalHeaders = canonicalHeaderNames.map((name) => `${name}:${String(lower[name]).trim()}
|
|
171
|
+
`).join("");
|
|
172
|
+
const signedHeaders = canonicalHeaderNames.join(";");
|
|
173
|
+
const canonicalQuery = [...args.url.searchParams.entries()].map(([name, value]) => [encodeSegment(name), encodeSegment(value)]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : 1).map(([name, value]) => `${name}=${value}`).join("&");
|
|
174
|
+
const canonicalRequest = [
|
|
175
|
+
args.method,
|
|
176
|
+
args.url.pathname,
|
|
177
|
+
canonicalQuery,
|
|
178
|
+
canonicalHeaders,
|
|
179
|
+
signedHeaders,
|
|
180
|
+
args.payloadHash
|
|
181
|
+
].join(`
|
|
182
|
+
`);
|
|
183
|
+
const scope = `${short}/${args.region}/${service}/aws4_request`;
|
|
184
|
+
const stringToSign = [
|
|
185
|
+
"AWS4-HMAC-SHA256",
|
|
186
|
+
full,
|
|
187
|
+
scope,
|
|
188
|
+
await sha256Hex(canonicalRequest)
|
|
189
|
+
].join(`
|
|
190
|
+
`);
|
|
191
|
+
let key = encoder.encode(`AWS4${args.credentials.secretAccessKey}`);
|
|
192
|
+
for (const part of [short, args.region, service, "aws4_request"])
|
|
193
|
+
key = await hmac(key, part);
|
|
194
|
+
const signature = hex(await hmac(key, stringToSign));
|
|
195
|
+
return {
|
|
196
|
+
url: args.url.toString(),
|
|
197
|
+
method: args.method,
|
|
198
|
+
headers: {
|
|
199
|
+
...headers,
|
|
200
|
+
authorization: `AWS4-HMAC-SHA256 Credential=${args.credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function createS3Client(config, credentials) {
|
|
205
|
+
const doFetch = config.fetch ?? globalThis.fetch;
|
|
206
|
+
const timeoutMs = config.timeoutMs ?? 30000;
|
|
207
|
+
const addressing = config.addressing ?? "path";
|
|
208
|
+
function urlFor(key, query = {}) {
|
|
209
|
+
const base = new URL(config.endpoint);
|
|
210
|
+
if (addressing === "virtual") {
|
|
211
|
+
base.host = `${config.bucket}.${base.host}`;
|
|
212
|
+
base.pathname = `/${encodeKey(key)}`;
|
|
213
|
+
} else {
|
|
214
|
+
base.pathname = key ? `/${encodeSegment(config.bucket)}/${encodeKey(key)}` : `/${encodeSegment(config.bucket)}`;
|
|
215
|
+
}
|
|
216
|
+
for (const [name, value] of Object.entries(query))
|
|
217
|
+
base.searchParams.set(name, value);
|
|
218
|
+
return base;
|
|
219
|
+
}
|
|
220
|
+
async function send(args) {
|
|
221
|
+
const payloadHash = await sha256Hex(args.body ?? "");
|
|
222
|
+
const signed = await signRequest({
|
|
223
|
+
method: args.method,
|
|
224
|
+
url: args.url,
|
|
225
|
+
region: config.region,
|
|
226
|
+
credentials,
|
|
227
|
+
payloadHash,
|
|
228
|
+
headers: args.headers,
|
|
229
|
+
at: new Date
|
|
230
|
+
});
|
|
231
|
+
const controller = new AbortController;
|
|
232
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
233
|
+
try {
|
|
234
|
+
const response = await doFetch(signed.url, {
|
|
235
|
+
method: signed.method,
|
|
236
|
+
headers: signed.headers,
|
|
237
|
+
signal: controller.signal,
|
|
238
|
+
...args.body ? { body: args.body } : {}
|
|
239
|
+
});
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
const detail = await response.text().catch(() => "");
|
|
242
|
+
throw new StorageError(`S3_${response.status}`, `${args.method} ${args.url.pathname} → ${response.status}. ${extractS3Message(detail) ?? detail.slice(0, 200)}`, response.status);
|
|
243
|
+
}
|
|
244
|
+
return response;
|
|
245
|
+
} finally {
|
|
246
|
+
clearTimeout(timer);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
async putObject(args) {
|
|
251
|
+
const body = typeof args.body === "string" ? encoder.encode(args.body) : args.body;
|
|
252
|
+
if (body.byteLength > MAX_SINGLE_PUT_BYTES) {
|
|
253
|
+
throw new StorageError("S3_TOO_LARGE", `${body.byteLength} bytes exceeds the ${MAX_SINGLE_PUT_BYTES}-byte single-request limit. This needs multipart upload, which this client does not implement.`);
|
|
254
|
+
}
|
|
255
|
+
const response = await send({
|
|
256
|
+
method: "PUT",
|
|
257
|
+
url: urlFor(args.key),
|
|
258
|
+
body,
|
|
259
|
+
headers: {
|
|
260
|
+
"content-type": args.contentType ?? "application/octet-stream",
|
|
261
|
+
...Object.fromEntries(Object.entries(args.metadata ?? {}).map(([name, value]) => [`x-amz-meta-${name}`, value]))
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
return { etag: response.headers.get("etag") ?? undefined };
|
|
265
|
+
},
|
|
266
|
+
async getObject(key) {
|
|
267
|
+
const response = await send({ method: "GET", url: urlFor(key) });
|
|
268
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
269
|
+
},
|
|
270
|
+
async headObject(key) {
|
|
271
|
+
try {
|
|
272
|
+
const response = await send({ method: "HEAD", url: urlFor(key) });
|
|
273
|
+
return {
|
|
274
|
+
key,
|
|
275
|
+
size: Number(response.headers.get("content-length") ?? 0),
|
|
276
|
+
etag: response.headers.get("etag") ?? undefined,
|
|
277
|
+
lastModified: response.headers.get("last-modified") ?? undefined
|
|
278
|
+
};
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (error instanceof StorageError && error.status === 404)
|
|
281
|
+
return null;
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
async deleteObject(key) {
|
|
286
|
+
await send({ method: "DELETE", url: urlFor(key) });
|
|
287
|
+
},
|
|
288
|
+
async listObjects(prefix = "", limit = 1000) {
|
|
289
|
+
const found = [];
|
|
290
|
+
let token;
|
|
291
|
+
do {
|
|
292
|
+
const response = await send({
|
|
293
|
+
method: "GET",
|
|
294
|
+
url: urlFor("", {
|
|
295
|
+
"list-type": "2",
|
|
296
|
+
...prefix ? { prefix } : {},
|
|
297
|
+
"max-keys": String(Math.min(1000, limit - found.length)),
|
|
298
|
+
...token ? { "continuation-token": token } : {}
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
const xml = await response.text();
|
|
302
|
+
found.push(...parseListXml(xml));
|
|
303
|
+
token = between(xml, "<NextContinuationToken>", "</NextContinuationToken>");
|
|
304
|
+
} while (token && found.length < limit);
|
|
305
|
+
return found.slice(0, limit);
|
|
306
|
+
},
|
|
307
|
+
async presign(args) {
|
|
308
|
+
const expires = Math.min(args.expiresInSec ?? 300, 604800);
|
|
309
|
+
const at = new Date;
|
|
310
|
+
const { full, short } = amzDate(at);
|
|
311
|
+
const scope = `${short}/${config.region}/s3/aws4_request`;
|
|
312
|
+
const url = urlFor(args.key);
|
|
313
|
+
url.searchParams.set("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
|
|
314
|
+
url.searchParams.set("X-Amz-Credential", `${credentials.accessKeyId}/${scope}`);
|
|
315
|
+
url.searchParams.set("X-Amz-Date", full);
|
|
316
|
+
url.searchParams.set("X-Amz-Expires", String(expires));
|
|
317
|
+
url.searchParams.set("X-Amz-SignedHeaders", "host");
|
|
318
|
+
if (credentials.sessionToken)
|
|
319
|
+
url.searchParams.set("X-Amz-Security-Token", credentials.sessionToken);
|
|
320
|
+
const signed = await signRequest({
|
|
321
|
+
method: args.method ?? "GET",
|
|
322
|
+
url,
|
|
323
|
+
region: config.region,
|
|
324
|
+
credentials,
|
|
325
|
+
payloadHash: "UNSIGNED-PAYLOAD",
|
|
326
|
+
at
|
|
327
|
+
});
|
|
328
|
+
const signature = /Signature=([a-f0-9]+)/.exec(signed.headers.authorization)?.[1] ?? "";
|
|
329
|
+
url.searchParams.set("X-Amz-Signature", signature);
|
|
330
|
+
return url.toString();
|
|
331
|
+
},
|
|
332
|
+
bucket: config.bucket,
|
|
333
|
+
endpoint: config.endpoint
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
var between = (text, open, close) => {
|
|
337
|
+
const start = text.indexOf(open);
|
|
338
|
+
if (start === -1)
|
|
339
|
+
return;
|
|
340
|
+
const end = text.indexOf(close, start + open.length);
|
|
341
|
+
return end === -1 ? undefined : text.slice(start + open.length, end);
|
|
342
|
+
};
|
|
343
|
+
var unescapeXml = (value) => value.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
344
|
+
function parseListXml(xml) {
|
|
345
|
+
const objects = [];
|
|
346
|
+
for (const match of xml.matchAll(/<Contents>([\s\S]*?)<\/Contents>/g)) {
|
|
347
|
+
const block = match[1];
|
|
348
|
+
const key = between(block, "<Key>", "</Key>");
|
|
349
|
+
if (!key)
|
|
350
|
+
continue;
|
|
351
|
+
objects.push({
|
|
352
|
+
key: unescapeXml(key),
|
|
353
|
+
size: Number(between(block, "<Size>", "</Size>") ?? 0),
|
|
354
|
+
etag: between(block, "<ETag>", "</ETag>")?.replace(/"|"/g, ""),
|
|
355
|
+
lastModified: between(block, "<LastModified>", "</LastModified>")
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return objects;
|
|
359
|
+
}
|
|
360
|
+
var extractS3Message = (xml) => {
|
|
361
|
+
const code = between(xml, "<Code>", "</Code>");
|
|
362
|
+
const message = between(xml, "<Message>", "</Message>");
|
|
363
|
+
return code || message ? `${code ?? ""}${code && message ? ": " : ""}${message ?? ""}` : undefined;
|
|
364
|
+
};
|
|
365
|
+
var s3 = defineProvider({
|
|
366
|
+
id: "s3",
|
|
367
|
+
service: "storage",
|
|
368
|
+
label: "S3-compatible storage",
|
|
369
|
+
multiInstance: true,
|
|
370
|
+
credentials: {
|
|
371
|
+
type: "object",
|
|
372
|
+
additionalProperties: false,
|
|
373
|
+
required: ["accessKeyId", "secretAccessKey"],
|
|
374
|
+
properties: {
|
|
375
|
+
accessKeyId: { type: "string", title: "Access key ID" },
|
|
376
|
+
secretAccessKey: { type: "string", title: "Secret access key", writeOnly: true },
|
|
377
|
+
sessionToken: { type: "string", title: "Session token", writeOnly: true }
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
config: {
|
|
381
|
+
type: "object",
|
|
382
|
+
additionalProperties: false,
|
|
383
|
+
required: ["endpoint", "region", "bucket"],
|
|
384
|
+
properties: {
|
|
385
|
+
endpoint: { type: "string", title: "Endpoint", description: "https://s3.eu-central-1.amazonaws.com" },
|
|
386
|
+
region: { type: "string", title: "Region", default: "us-east-1" },
|
|
387
|
+
bucket: { type: "string", title: "Bucket" },
|
|
388
|
+
addressing: {
|
|
389
|
+
type: "string",
|
|
390
|
+
enum: ["path", "virtual"],
|
|
391
|
+
default: "path",
|
|
392
|
+
description: "Path style works everywhere except AWS itself; virtual style is bucket.host. Guessing wrong presents as a 404 rather than a configuration error."
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
async invoke(context, request) {
|
|
397
|
+
const config = context.config;
|
|
398
|
+
const client = createS3Client(config, {
|
|
399
|
+
accessKeyId: await context.secret("accessKeyId"),
|
|
400
|
+
secretAccessKey: await context.secret("secretAccessKey")
|
|
401
|
+
});
|
|
402
|
+
switch (request.op) {
|
|
403
|
+
case "put":
|
|
404
|
+
return client.putObject(request);
|
|
405
|
+
case "get":
|
|
406
|
+
return client.getObject(request.key);
|
|
407
|
+
case "head":
|
|
408
|
+
return client.headObject(request.key);
|
|
409
|
+
case "delete":
|
|
410
|
+
return client.deleteObject(request.key);
|
|
411
|
+
case "list":
|
|
412
|
+
return client.listObjects(request.prefix, request.limit);
|
|
413
|
+
case "presign":
|
|
414
|
+
return client.presign(request);
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
classify(error) {
|
|
418
|
+
const status = error.status;
|
|
419
|
+
if (status === 401 || status === 403)
|
|
420
|
+
return "retryable";
|
|
421
|
+
if (status === 404)
|
|
422
|
+
return "terminal";
|
|
423
|
+
if (status === 400 || status === 411 || status === 413)
|
|
424
|
+
return "terminal";
|
|
425
|
+
if (status === 429 || status === 503)
|
|
426
|
+
return "backoff";
|
|
427
|
+
return "retryable";
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
var storageProviders = [s3];
|
|
431
|
+
export {
|
|
432
|
+
storageProviders,
|
|
433
|
+
signRequest,
|
|
434
|
+
s3,
|
|
435
|
+
parseListXml,
|
|
436
|
+
encodeSegment,
|
|
437
|
+
createS3Client,
|
|
438
|
+
amzDate,
|
|
439
|
+
StorageError,
|
|
440
|
+
MAX_SINGLE_PUT_BYTES
|
|
441
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
|
|
3
|
+
"name": "@forgezero/providers",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./email": {
|
|
15
|
+
"types": "./dist/email.d.ts",
|
|
16
|
+
"default": "./dist/email.js"
|
|
17
|
+
},
|
|
18
|
+
"./chain": {
|
|
19
|
+
"types": "./dist/chain.d.ts",
|
|
20
|
+
"default": "./dist/chain.js"
|
|
21
|
+
},
|
|
22
|
+
"./database": {
|
|
23
|
+
"types": "./dist/database.d.ts",
|
|
24
|
+
"default": "./dist/database.js"
|
|
25
|
+
},
|
|
26
|
+
"./http": {
|
|
27
|
+
"types": "./dist/http.d.ts",
|
|
28
|
+
"default": "./dist/http.js"
|
|
29
|
+
},
|
|
30
|
+
"./storage": {
|
|
31
|
+
"types": "./dist/storage.d.ts",
|
|
32
|
+
"default": "./dist/storage.js"
|
|
33
|
+
},
|
|
34
|
+
"./pool": {
|
|
35
|
+
"types": "./dist/pool.d.ts",
|
|
36
|
+
"default": "./dist/pool.js"
|
|
37
|
+
},
|
|
38
|
+
"./binance": {
|
|
39
|
+
"types": "./dist/binance.d.ts",
|
|
40
|
+
"default": "./dist/binance.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"check": "tsc --noEmit",
|
|
45
|
+
"build": "bun build src/index.ts src/email.ts src/chain.ts src/database.ts src/http.ts src/pool.ts src/storage.ts src/binance.ts --root src --outdir dist --target browser --format esm --packages external && tsc --emitDeclarationOnly --declaration --noEmit false --outDir dist",
|
|
46
|
+
"prepublishOnly": "bun run check && bun run build"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"typescript": "^5.6.0",
|
|
50
|
+
"@types/bun": "latest"
|
|
51
|
+
},
|
|
52
|
+
"description": "Service registry with priority, health and fallback. Bring your own credential and config sources.",
|
|
53
|
+
"keywords": [
|
|
54
|
+
"email",
|
|
55
|
+
"smtp",
|
|
56
|
+
"failover",
|
|
57
|
+
"fallback",
|
|
58
|
+
"service-registry",
|
|
59
|
+
"retry",
|
|
60
|
+
"s3",
|
|
61
|
+
"storage",
|
|
62
|
+
"object-storage"
|
|
63
|
+
],
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"homepage": "https://forgezero.net/docs/providers",
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "git+https://github.com/axxra/forgezero.git",
|
|
69
|
+
"directory": "packages/providers"
|
|
70
|
+
},
|
|
71
|
+
"bugs": "https://github.com/axxra/forgezero/issues",
|
|
72
|
+
"sideEffects": false,
|
|
73
|
+
"types": "./dist/index.d.ts",
|
|
74
|
+
"files": [
|
|
75
|
+
"dist",
|
|
76
|
+
"README.md",
|
|
77
|
+
"LICENSE"
|
|
78
|
+
],
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"@forgezero/runtime": "^0.1.0"
|
|
81
|
+
}
|
|
82
|
+
}
|