@artblocks/abx-storage 0.1.0-alpha.1 → 0.1.0-alpha.10
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/arweave.d.ts +14 -0
- package/dist/arweave.d.ts.map +1 -1
- package/dist/arweave.js +40 -1
- package/dist/arweave.js.map +1 -1
- package/dist/content-plan.d.ts +78 -0
- package/dist/content-plan.d.ts.map +1 -0
- package/dist/content-plan.js +68 -0
- package/dist/content-plan.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +37 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +55 -0
- package/dist/migrate.js.map +1 -0
- package/dist/probe.d.ts +66 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +155 -0
- package/dist/probe.js.map +1 -0
- package/dist/readiness.d.ts +157 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +189 -0
- package/dist/readiness.js.map +1 -0
- package/dist/upload-readiness.d.ts +89 -0
- package/dist/upload-readiness.d.ts.map +1 -0
- package/dist/upload-readiness.js +85 -0
- package/dist/upload-readiness.js.map +1 -0
- package/dist/upload.d.ts +28 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +41 -0
- package/dist/upload.js.map +1 -0
- package/package.json +2 -2
package/dist/probe.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { arweaveFunding, turboIdentity, turboIdentityAddress, isEthIdentity } from './arweave.js';
|
|
3
|
+
import { resolveBackend } from './resolve.js';
|
|
4
|
+
/** Matches readiness.ts's gateway-probe default — this module's other real-network default. */
|
|
5
|
+
const DEFAULT_CHECK_TIMEOUT_MS = 6000;
|
|
6
|
+
/** Race a call against a `ms` deadline, rejecting with `message` if it wins. Most methods probed
|
|
7
|
+
* here have no abort hook of their own, so this stops WAITING for a hung request rather than
|
|
8
|
+
* cancelling it — good enough to keep a caller (doctor) from hanging. Always clears its timer, so
|
|
9
|
+
* a fast call doesn't leave one pending. */
|
|
10
|
+
async function race(p, ms, message) {
|
|
11
|
+
let timer;
|
|
12
|
+
const timeout = new Promise((_, reject) => {
|
|
13
|
+
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
return await Promise.race([p, timeout]);
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** fs + ipfs (and any future backend with nothing more to add): delegate straight to the existing
|
|
23
|
+
* `health()` — see the module doc for why this is reuse, not a stand-in pending real logic. */
|
|
24
|
+
export async function checkViaHealth(backend, opts = {}) {
|
|
25
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
26
|
+
try {
|
|
27
|
+
const h = await race(backend.health?.() ?? Promise.resolve({ ok: true, detail: 'no check defined for this backend' }), timeoutMs, `timed out after ${timeoutMs}ms`);
|
|
28
|
+
return { backend: backend.id, ok: h.ok, detail: h.detail ?? '' };
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return { backend: backend.id, ok: false, detail: e.message };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `cloud`: PUT a tiny probe object through the signed API, then GET it back with a plain,
|
|
36
|
+
* UNSIGNED fetch against `publicBase` — the same request a marketplace/browser would make. Reuses
|
|
37
|
+
* one well-known key (`abx-probe/check.txt`) rather than minting a new one per run, so repeated
|
|
38
|
+
* checks don't litter the bucket. `cfg` (when given) names the API write target too — the R2/S3
|
|
39
|
+
* endpoint-vs-public-base trap is only diagnosable if BOTH URLs are on screen, not just the one
|
|
40
|
+
* that failed.
|
|
41
|
+
*/
|
|
42
|
+
export async function checkCloudBackend(backend, cfg, opts = {}) {
|
|
43
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
44
|
+
const key = 'abx-probe/check.txt';
|
|
45
|
+
const putUrl = cfg ? `${cfg.endpoint.replace(/\/+$/, '')}/${cfg.bucket}/${key}` : undefined;
|
|
46
|
+
if (!backend.publicBase) {
|
|
47
|
+
return {
|
|
48
|
+
backend: 'cloud',
|
|
49
|
+
ok: false,
|
|
50
|
+
detail: "no public read base configured — set ABX_S3_PUBLIC_BASE (or --public-base) to the bucket's public URL.",
|
|
51
|
+
putUrl,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (!backend.putObject || !backend.getObject) {
|
|
55
|
+
return { backend: 'cloud', ok: false, detail: 'this cloud backend has no putObject/getObject — cannot round-trip check it.', putUrl };
|
|
56
|
+
}
|
|
57
|
+
const publicUrl = `${backend.publicBase.replace(/\/+$/, '')}/${key}`;
|
|
58
|
+
const token = randomBytes(8).toString('hex');
|
|
59
|
+
const body = `abx storage check ${token}`;
|
|
60
|
+
const bytes = new TextEncoder().encode(body);
|
|
61
|
+
try {
|
|
62
|
+
await race(backend.putObject(key, { bytes, contentType: 'text/plain' }), timeoutMs, `PUT timed out after ${timeoutMs}ms`);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return { backend: 'cloud', ok: false, detail: `PUT via the API failed: ${e.message}`, putUrl, publicUrl };
|
|
66
|
+
}
|
|
67
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
68
|
+
const ctrl = new AbortController();
|
|
69
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetchFn(publicUrl, { signal: ctrl.signal });
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
return {
|
|
74
|
+
backend: 'cloud',
|
|
75
|
+
ok: false,
|
|
76
|
+
detail: `GET via the public base failed (${res.status}) — the public base may point at a different bucket/endpoint than the write API.`,
|
|
77
|
+
putUrl,
|
|
78
|
+
publicUrl,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const got = new TextDecoder().decode(new Uint8Array(await res.arrayBuffer()));
|
|
82
|
+
if (got !== body) {
|
|
83
|
+
return {
|
|
84
|
+
backend: 'cloud',
|
|
85
|
+
ok: false,
|
|
86
|
+
detail: 'GET via the public base returned different bytes than were PUT — check for a caching layer or a public base pointed at a different bucket.',
|
|
87
|
+
putUrl,
|
|
88
|
+
publicUrl,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return { backend: 'cloud', ok: true, detail: `round-trip ok via ${publicUrl}`, putUrl, publicUrl };
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
const aborted = e.name === 'AbortError';
|
|
95
|
+
return {
|
|
96
|
+
backend: 'cloud',
|
|
97
|
+
ok: false,
|
|
98
|
+
detail: aborted ? `GET via the public base timed out after ${timeoutMs}ms` : `GET via the public base failed: ${e.message}`,
|
|
99
|
+
putUrl,
|
|
100
|
+
publicUrl,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* `arweave`: the existing `health()` (gateway reachability + identity presence) plus a Turbo
|
|
109
|
+
* BALANCE read when an identity already exists — never a paid upload (no identity yet is reported
|
|
110
|
+
* as-is: it's created free on first real upload, so there's nothing to check).
|
|
111
|
+
*/
|
|
112
|
+
export async function checkArweaveBackend(backend, cfg, opts = {}) {
|
|
113
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
114
|
+
let base;
|
|
115
|
+
try {
|
|
116
|
+
base = await race(backend.health?.() ?? Promise.resolve({ ok: true, detail: '' }), timeoutMs, `timed out after ${timeoutMs}ms`);
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
base = { ok: false, detail: e.message };
|
|
120
|
+
}
|
|
121
|
+
if (!cfg)
|
|
122
|
+
return { backend: 'arweave', ok: base.ok, detail: base.detail ?? '' };
|
|
123
|
+
const id = turboIdentity(cfg);
|
|
124
|
+
if (!id) {
|
|
125
|
+
return { backend: 'arweave', ok: base.ok, detail: [base.detail, 'no identity yet — created free on first upload (nothing to check)'].filter(Boolean).join(' · ') };
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const funding = arweaveFunding(cfg);
|
|
129
|
+
const { credits } = await race(funding.balance(), timeoutMs, `balance check timed out after ${timeoutMs}ms`);
|
|
130
|
+
const address = turboIdentityAddress(id);
|
|
131
|
+
return {
|
|
132
|
+
backend: 'arweave',
|
|
133
|
+
ok: base.ok,
|
|
134
|
+
detail: [base.detail, `${credits} credits (identity ${address}${isEthIdentity(id) ? ', eth' : ''})`].filter(Boolean).join(' · '),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
return { backend: 'arweave', ok: base.ok, detail: [base.detail, `balance check failed: ${e.message}`].filter(Boolean).join(' · ') };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The one storage-config probe both `abx storage show --check` and `abx doctor` call — dispatches to
|
|
143
|
+
* the right per-backend check above. Resolving the backend can throw (missing required config, e.g.
|
|
144
|
+
* cloud with no endpoint/bucket) — same as every other `resolveBackend` call site, and the caller's
|
|
145
|
+
* existing try/catch around it is the right place to handle that, not this function.
|
|
146
|
+
*/
|
|
147
|
+
export async function probeStorageBackend(opts, checkOpts = {}) {
|
|
148
|
+
const backend = resolveBackend(opts);
|
|
149
|
+
if (backend.id === 'cloud' || backend.id === 's3')
|
|
150
|
+
return checkCloudBackend(backend, opts.cloud, checkOpts);
|
|
151
|
+
if (backend.id === 'arweave')
|
|
152
|
+
return checkArweaveBackend(backend, opts.arweave, checkOpts);
|
|
153
|
+
return checkViaHealth(backend, checkOpts);
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is a locator **retrievable** yet — not just accepted?
|
|
3
|
+
*
|
|
4
|
+
* An upload service answers "accepted" the moment it has your bytes; a gateway answers "serving"
|
|
5
|
+
* only once they have propagated to it, and on Arweave that gap runs to minutes. Nothing in the
|
|
6
|
+
* upload result distinguishes the two, so the natural implementation — upload during a mint, write
|
|
7
|
+
* the locator into the token — mints a token that renders broken for the first minutes of its life.
|
|
8
|
+
*
|
|
9
|
+
* Two independent integrators hit this eight days apart. The first rebuilt this layer themselves
|
|
10
|
+
* (ranged GETs, a propagating/ready model, retry ladders lengthened after measuring real times) and
|
|
11
|
+
* concluded "every serious integrator will rebuild some version of this." The second published 32
|
|
12
|
+
* renders and found **32/32 404ing on `arweave.net` while 22/32 already served from `permagate.io`
|
|
13
|
+
* and `vilenarios.com`**, with the uploader reporting `CONFIRMED` throughout. That second
|
|
14
|
+
* observation is why this probes several gateways rather than one: propagation is per-gateway, so
|
|
15
|
+
* "your gateway doesn't have it" and "the network doesn't have it" are different answers, and only
|
|
16
|
+
* one of them means something is wrong.
|
|
17
|
+
*/
|
|
18
|
+
/** The locator's network, which decides which gateways can be asked about it. */
|
|
19
|
+
export type LocatorNetwork = 'arweave' | 'ipfs' | 'http';
|
|
20
|
+
export type Readiness =
|
|
21
|
+
/** The gateway this toolkit would actually use serves the bytes. Safe to reference. */
|
|
22
|
+
'ready'
|
|
23
|
+
/** Another gateway serves it, so the data provably exists on the network — yours hasn't caught
|
|
24
|
+
* up. Waiting is the fix. */
|
|
25
|
+
| 'propagating'
|
|
26
|
+
/** No probed gateway serves it. Deliberately NOT called "propagating": from outside, a locator
|
|
27
|
+
* that is still settling and one that is simply wrong look identical, and claiming the friendlier
|
|
28
|
+
* of the two is how a tool teaches someone to ignore it. */
|
|
29
|
+
| 'unreachable';
|
|
30
|
+
export interface GatewayProbe {
|
|
31
|
+
/** The exact URL asked. */
|
|
32
|
+
url: string;
|
|
33
|
+
/** Host only — what a human recognises. */
|
|
34
|
+
gateway: string;
|
|
35
|
+
/** Whether this gateway served the bytes. */
|
|
36
|
+
serving: boolean;
|
|
37
|
+
/** HTTP status, or null when the request never completed (timeout, DNS, refused). */
|
|
38
|
+
status: number | null;
|
|
39
|
+
contentType: string | null;
|
|
40
|
+
/** Total size when the gateway reported one, else null. A ranged request is used, so this comes
|
|
41
|
+
* from `content-range`'s total rather than `content-length` (which is 1 byte on a 206). */
|
|
42
|
+
bytes: number | null;
|
|
43
|
+
ms: number;
|
|
44
|
+
/** Why the request didn't complete. Absent when it did, whatever the status. */
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface LocatorStatus {
|
|
48
|
+
/** The locator as given. */
|
|
49
|
+
locator: string;
|
|
50
|
+
network: LocatorNetwork;
|
|
51
|
+
/** The txid / CID / absolute URL the locator resolves to. */
|
|
52
|
+
id: string;
|
|
53
|
+
readiness: Readiness;
|
|
54
|
+
/** The gateway a baked locator would actually resolve through — the one that decides whether a
|
|
55
|
+
* marketplace can render this today. */
|
|
56
|
+
primary: GatewayProbe;
|
|
57
|
+
/** Well-known others for the same network. Their whole job is to tell propagation from a bad id. */
|
|
58
|
+
alternates: GatewayProbe[];
|
|
59
|
+
}
|
|
60
|
+
export interface LocatorStatusOptions {
|
|
61
|
+
/** The gateway to treat as primary — normally the one the active backend is configured with, so
|
|
62
|
+
* the verdict is about the gateway that will really be used. Defaults per network. */
|
|
63
|
+
gateway?: string;
|
|
64
|
+
/** Skip the alternates (one request instead of three). The cost is the `propagating` verdict:
|
|
65
|
+
* without them, a locator that hasn't reached your gateway is indistinguishable from a bad one. */
|
|
66
|
+
primaryOnly?: boolean;
|
|
67
|
+
timeoutMs?: number;
|
|
68
|
+
fetchFn?: typeof fetch;
|
|
69
|
+
}
|
|
70
|
+
/** Parse any accepted locator form into its network + id. */
|
|
71
|
+
export declare function parseLocator(locator: string): {
|
|
72
|
+
network: LocatorNetwork;
|
|
73
|
+
id: string;
|
|
74
|
+
};
|
|
75
|
+
/** Build the URL that asks `gateway` for `id`. A locator that is already an absolute URL is asked
|
|
76
|
+
* verbatim — rewriting someone's URL would answer a question they didn't ask. */
|
|
77
|
+
export declare function gatewayUrlFor(network: LocatorNetwork, id: string, gateway: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* The gateway BASE for a locator network — **override → env → generic public default**
|
|
80
|
+
* (`ABX_IPFS_GATEWAY`/`ABX_ARWEAVE_GATEWAY`, then `ipfs.io`/`arweave.net`). This is the READ-TIME
|
|
81
|
+
* default for resolving an arbitrary on-chain locator whose upload backend is unknown to the
|
|
82
|
+
* resolver — distinct from {@link ipfsConfigFromEnv}/{@link arweaveConfigFromEnv}'s MODE-aware
|
|
83
|
+
* defaults for a backend this toolkit itself configured to write through (e.g. a `kubo` IPFS
|
|
84
|
+
* gateway defaults to `127.0.0.1:8080`, a locator resolver has no such context and must default
|
|
85
|
+
* to something that resolves for anyone). `http` locators carry their own base (they are already
|
|
86
|
+
* an absolute URL) and never reach here — callers return them verbatim before consulting a gateway.
|
|
87
|
+
*
|
|
88
|
+
* The one function every "turn a stored locator into a fetchable URL" caller shares — token-api's
|
|
89
|
+
* `resolveLocatorUrl`/`codeLocatorUrl` (`packages/token-api/src/code.ts`) used to each hand-roll
|
|
90
|
+
* this same `override ?? env ?? default` line twice; this module already had the identical logic
|
|
91
|
+
* private to `locatorStatus`, so exporting it (rather than a third private copy) is where it belongs.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveGatewayBase(network: LocatorNetwork, override?: string): string;
|
|
94
|
+
/**
|
|
95
|
+
* Ask one gateway whether it serves these bytes, reading **headers only**.
|
|
96
|
+
*
|
|
97
|
+
* A ranged request (`Range: bytes=0-0`) is what keeps this cheap: the art in question can be tens of
|
|
98
|
+
* megabytes and the question is only whether it is there. Gateways that ignore `Range` answer 200
|
|
99
|
+
* with the whole body, so the body is cancelled rather than read — without that, checking a large
|
|
100
|
+
* locator would download it.
|
|
101
|
+
*/
|
|
102
|
+
export declare function probeGateway(url: string, opts?: {
|
|
103
|
+
timeoutMs?: number;
|
|
104
|
+
fetchFn?: typeof fetch;
|
|
105
|
+
}): Promise<GatewayProbe>;
|
|
106
|
+
/** Accepted-vs-retrievable for one locator, across the gateway that matters and a couple of others. */
|
|
107
|
+
export declare function locatorStatus(locator: string, opts?: LocatorStatusOptions): Promise<LocatorStatus>;
|
|
108
|
+
/** One poll attempt from {@link awaitLocatorReady} — lets a caller narrate progress (a CLI spinner,
|
|
109
|
+
* a log line) without this module printing anything itself (the SDK/storage layers never print). */
|
|
110
|
+
export interface AwaitLocatorReadyEvent {
|
|
111
|
+
/** 1-indexed poll attempt. */
|
|
112
|
+
attempt: number;
|
|
113
|
+
/** Milliseconds since {@link awaitLocatorReady} was called. */
|
|
114
|
+
elapsedMs: number;
|
|
115
|
+
status: LocatorStatus;
|
|
116
|
+
}
|
|
117
|
+
export interface AwaitLocatorReadyOptions {
|
|
118
|
+
/** Forwarded to every {@link locatorStatus} probe. */
|
|
119
|
+
gateway?: string;
|
|
120
|
+
primaryOnly?: boolean;
|
|
121
|
+
/** Per-probe network timeout, forwarded as {@link LocatorStatusOptions.timeoutMs} (NOT the
|
|
122
|
+
* poll's overall deadline — see `timeoutMs` below). Default 6000ms, same as `locatorStatus`. */
|
|
123
|
+
probeTimeoutMs?: number;
|
|
124
|
+
fetchFn?: typeof fetch;
|
|
125
|
+
/** Give up polling once this much total time has elapsed, returning the last status with
|
|
126
|
+
* `ready: false` rather than throwing — a caller decides what "still not ready" means for its
|
|
127
|
+
* own flow. Default 5 minutes: long enough for typical Arweave propagation (this module's whole
|
|
128
|
+
* reason to exist — see the module doc, "the gap runs to minutes"). */
|
|
129
|
+
timeoutMs?: number;
|
|
130
|
+
/** Base delay for the linear backoff between polls (`attempt * pollBaseMs`, via the SDK's
|
|
131
|
+
* {@link linearBackoffDelay} — the same primitive `service.ts`'s retry ladder uses). Default
|
|
132
|
+
* 3000ms, so the very first re-poll is quick and later ones back off. */
|
|
133
|
+
pollBaseMs?: number;
|
|
134
|
+
onEvent?: (e: AwaitLocatorReadyEvent) => void;
|
|
135
|
+
}
|
|
136
|
+
export interface AwaitLocatorReadyResult {
|
|
137
|
+
/** The last status observed — `'ready'` iff {@link ready} is true. */
|
|
138
|
+
status: LocatorStatus;
|
|
139
|
+
attempts: number;
|
|
140
|
+
elapsedMs: number;
|
|
141
|
+
/** False when the deadline was hit before `status.readiness` reached `'ready'`. */
|
|
142
|
+
ready: boolean;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Poll {@link locatorStatus} until it reports `'ready'` or the deadline passes.
|
|
146
|
+
*
|
|
147
|
+
* `locatorStatus` alone answers "right now" — the natural next question, for a caller that would
|
|
148
|
+
* otherwise upload during a mint and write the locator into a token that renders broken for its
|
|
149
|
+
* first minutes of life (see the module doc), is "wait until it's actually there." This is that,
|
|
150
|
+
* factored out once so a caller doesn't hand-roll its own poll/backoff loop around a one-shot check.
|
|
151
|
+
*
|
|
152
|
+
* `'propagating'` is treated the same as `'unreachable'` here — both mean "not yet what THIS caller
|
|
153
|
+
* can rely on" — but every intermediate status still reaches `onEvent`, so a caller that wants to
|
|
154
|
+
* distinguish "provably on the network, just behind" from "no evidence yet" can from the events.
|
|
155
|
+
*/
|
|
156
|
+
export declare function awaitLocatorReady(locator: string, opts?: AwaitLocatorReadyOptions): Promise<AwaitLocatorReadyResult>;
|
|
157
|
+
//# sourceMappingURL=readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { linearBackoffDelay, sleep } from '@artblocks/abx-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Gateways probed as alternates, per network. Kept short on purpose — this is a diagnosis, not a
|
|
4
|
+
* survey, and each entry is a request someone waits on. The Arweave pair is the one the second
|
|
5
|
+
* report actually measured serving ahead of `arweave.net`, so they are evidence rather than taste.
|
|
6
|
+
*/
|
|
7
|
+
const ALTERNATE_GATEWAYS = {
|
|
8
|
+
arweave: ['https://permagate.io', 'https://vilenarios.com'],
|
|
9
|
+
ipfs: ['https://dweb.link', 'https://w3s.link'],
|
|
10
|
+
};
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 6000;
|
|
12
|
+
/** Parse any accepted locator form into its network + id. */
|
|
13
|
+
export function parseLocator(locator) {
|
|
14
|
+
const raw = locator.trim();
|
|
15
|
+
if (/^ar:\/\//i.test(raw))
|
|
16
|
+
return { network: 'arweave', id: raw.slice(5).replace(/^\/+/, '') };
|
|
17
|
+
if (/^ipfs:\/\//i.test(raw))
|
|
18
|
+
return { network: 'ipfs', id: raw.slice(7).replace(/^\/+/, '') };
|
|
19
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
20
|
+
// A gateway URL already names its own gateway; keep it whole and ask exactly it. Detecting the
|
|
21
|
+
// network from the shape lets the alternates still apply (that is the point of the whole probe).
|
|
22
|
+
if (/\/ipfs\//.test(raw) || /\.ipfs\./.test(raw))
|
|
23
|
+
return { network: 'ipfs', id: raw };
|
|
24
|
+
if (/arweave\.net|permagate\.io|vilenarios\.com|ar-io\.dev/.test(raw))
|
|
25
|
+
return { network: 'arweave', id: raw };
|
|
26
|
+
return { network: 'http', id: raw };
|
|
27
|
+
}
|
|
28
|
+
// Bare ids: an Arweave txid is 43 chars of base64url; a CIDv0 starts Qm…, a CIDv1 b…/f…
|
|
29
|
+
if (/^[A-Za-z0-9_-]{43}$/.test(raw))
|
|
30
|
+
return { network: 'arweave', id: raw };
|
|
31
|
+
if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|b[a-z2-7]{20,}|f[0-9a-f]{20,})/.test(raw))
|
|
32
|
+
return { network: 'ipfs', id: raw };
|
|
33
|
+
throw new Error(`'${locator}' isn't a recognisable locator. Expected ar://<txid>, ipfs://<cid>, an https:// gateway URL, or a bare txid/CID.`);
|
|
34
|
+
}
|
|
35
|
+
/** Build the URL that asks `gateway` for `id`. A locator that is already an absolute URL is asked
|
|
36
|
+
* verbatim — rewriting someone's URL would answer a question they didn't ask. */
|
|
37
|
+
export function gatewayUrlFor(network, id, gateway) {
|
|
38
|
+
if (/^https?:\/\//i.test(id))
|
|
39
|
+
return id;
|
|
40
|
+
const base = gateway.replace(/\/+$/, '');
|
|
41
|
+
// A path suffix (a directory manifest entry, e.g. `<txid>/index.html`) rides along untouched.
|
|
42
|
+
return network === 'ipfs' ? `${base}/ipfs/${id}` : `${base}/${id}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The gateway BASE for a locator network — **override → env → generic public default**
|
|
46
|
+
* (`ABX_IPFS_GATEWAY`/`ABX_ARWEAVE_GATEWAY`, then `ipfs.io`/`arweave.net`). This is the READ-TIME
|
|
47
|
+
* default for resolving an arbitrary on-chain locator whose upload backend is unknown to the
|
|
48
|
+
* resolver — distinct from {@link ipfsConfigFromEnv}/{@link arweaveConfigFromEnv}'s MODE-aware
|
|
49
|
+
* defaults for a backend this toolkit itself configured to write through (e.g. a `kubo` IPFS
|
|
50
|
+
* gateway defaults to `127.0.0.1:8080`, a locator resolver has no such context and must default
|
|
51
|
+
* to something that resolves for anyone). `http` locators carry their own base (they are already
|
|
52
|
+
* an absolute URL) and never reach here — callers return them verbatim before consulting a gateway.
|
|
53
|
+
*
|
|
54
|
+
* The one function every "turn a stored locator into a fetchable URL" caller shares — token-api's
|
|
55
|
+
* `resolveLocatorUrl`/`codeLocatorUrl` (`packages/token-api/src/code.ts`) used to each hand-roll
|
|
56
|
+
* this same `override ?? env ?? default` line twice; this module already had the identical logic
|
|
57
|
+
* private to `locatorStatus`, so exporting it (rather than a third private copy) is where it belongs.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveGatewayBase(network, override) {
|
|
60
|
+
if (override)
|
|
61
|
+
return override;
|
|
62
|
+
if (network === 'ipfs')
|
|
63
|
+
return process.env.ABX_IPFS_GATEWAY ?? 'https://ipfs.io';
|
|
64
|
+
if (network === 'arweave')
|
|
65
|
+
return process.env.ABX_ARWEAVE_GATEWAY ?? 'https://arweave.net';
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Ask one gateway whether it serves these bytes, reading **headers only**.
|
|
70
|
+
*
|
|
71
|
+
* A ranged request (`Range: bytes=0-0`) is what keeps this cheap: the art in question can be tens of
|
|
72
|
+
* megabytes and the question is only whether it is there. Gateways that ignore `Range` answer 200
|
|
73
|
+
* with the whole body, so the body is cancelled rather than read — without that, checking a large
|
|
74
|
+
* locator would download it.
|
|
75
|
+
*/
|
|
76
|
+
export async function probeGateway(url, opts = {}) {
|
|
77
|
+
const doFetch = opts.fetchFn ?? fetch;
|
|
78
|
+
const gateway = safeHost(url);
|
|
79
|
+
const started = Date.now();
|
|
80
|
+
const ctrl = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
82
|
+
try {
|
|
83
|
+
const res = await doFetch(url, { headers: { range: 'bytes=0-0' }, signal: ctrl.signal, redirect: 'follow' });
|
|
84
|
+
// Never read the body: a Range-ignoring gateway would otherwise stream the whole asset.
|
|
85
|
+
try {
|
|
86
|
+
await res.body?.cancel();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* already consumed or unsupported — nothing to release */
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
url,
|
|
93
|
+
gateway,
|
|
94
|
+
serving: res.status >= 200 && res.status < 300,
|
|
95
|
+
status: res.status,
|
|
96
|
+
contentType: res.headers.get('content-type'),
|
|
97
|
+
bytes: totalBytesFrom(res.headers.get('content-range'), res.headers.get('content-length'), res.status),
|
|
98
|
+
ms: Date.now() - started,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
const aborted = err.name === 'AbortError' || ctrl.signal.aborted;
|
|
103
|
+
return {
|
|
104
|
+
url,
|
|
105
|
+
gateway,
|
|
106
|
+
serving: false,
|
|
107
|
+
status: null,
|
|
108
|
+
contentType: null,
|
|
109
|
+
bytes: null,
|
|
110
|
+
ms: Date.now() - started,
|
|
111
|
+
error: aborted ? `no response in ${opts.timeoutMs ?? DEFAULT_TIMEOUT_MS}ms` : err.message,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Accepted-vs-retrievable for one locator, across the gateway that matters and a couple of others. */
|
|
119
|
+
export async function locatorStatus(locator, opts = {}) {
|
|
120
|
+
const { network, id } = parseLocator(locator);
|
|
121
|
+
const primaryGateway = resolveGatewayBase(network, opts.gateway);
|
|
122
|
+
const primaryUrl = gatewayUrlFor(network, id, primaryGateway);
|
|
123
|
+
// `http` locators have no notion of an alternate — the URL IS the address, so a second host would
|
|
124
|
+
// be answering about different bytes.
|
|
125
|
+
const alternateBases = opts.primaryOnly || network === 'http' ? [] : ALTERNATE_GATEWAYS[network];
|
|
126
|
+
const alternateUrls = alternateBases
|
|
127
|
+
.map((base) => gatewayUrlFor(network, id, base))
|
|
128
|
+
.filter((u) => u !== primaryUrl);
|
|
129
|
+
const probe = (u) => probeGateway(u, { timeoutMs: opts.timeoutMs, fetchFn: opts.fetchFn });
|
|
130
|
+
const [primary, ...alternates] = await Promise.all([probe(primaryUrl), ...alternateUrls.map(probe)]);
|
|
131
|
+
const readiness = primary.serving
|
|
132
|
+
? 'ready'
|
|
133
|
+
: alternates.some((a) => a.serving)
|
|
134
|
+
? 'propagating'
|
|
135
|
+
: 'unreachable';
|
|
136
|
+
return { locator, network, id, readiness, primary, alternates };
|
|
137
|
+
}
|
|
138
|
+
const DEFAULT_AWAIT_TIMEOUT_MS = 5 * 60_000;
|
|
139
|
+
const DEFAULT_POLL_BASE_MS = 3000;
|
|
140
|
+
/**
|
|
141
|
+
* Poll {@link locatorStatus} until it reports `'ready'` or the deadline passes.
|
|
142
|
+
*
|
|
143
|
+
* `locatorStatus` alone answers "right now" — the natural next question, for a caller that would
|
|
144
|
+
* otherwise upload during a mint and write the locator into a token that renders broken for its
|
|
145
|
+
* first minutes of life (see the module doc), is "wait until it's actually there." This is that,
|
|
146
|
+
* factored out once so a caller doesn't hand-roll its own poll/backoff loop around a one-shot check.
|
|
147
|
+
*
|
|
148
|
+
* `'propagating'` is treated the same as `'unreachable'` here — both mean "not yet what THIS caller
|
|
149
|
+
* can rely on" — but every intermediate status still reaches `onEvent`, so a caller that wants to
|
|
150
|
+
* distinguish "provably on the network, just behind" from "no evidence yet" can from the events.
|
|
151
|
+
*/
|
|
152
|
+
export async function awaitLocatorReady(locator, opts = {}) {
|
|
153
|
+
const deadlineMs = opts.timeoutMs ?? DEFAULT_AWAIT_TIMEOUT_MS;
|
|
154
|
+
const pollBaseMs = opts.pollBaseMs ?? DEFAULT_POLL_BASE_MS;
|
|
155
|
+
const started = Date.now();
|
|
156
|
+
const probeOpts = { gateway: opts.gateway, primaryOnly: opts.primaryOnly, timeoutMs: opts.probeTimeoutMs, fetchFn: opts.fetchFn };
|
|
157
|
+
let attempt = 0;
|
|
158
|
+
for (;;) {
|
|
159
|
+
attempt++;
|
|
160
|
+
const status = await locatorStatus(locator, probeOpts);
|
|
161
|
+
const elapsedMs = Date.now() - started;
|
|
162
|
+
opts.onEvent?.({ attempt, elapsedMs, status });
|
|
163
|
+
if (status.readiness === 'ready')
|
|
164
|
+
return { status, attempts: attempt, elapsedMs, ready: true };
|
|
165
|
+
const delay = linearBackoffDelay(attempt, pollBaseMs);
|
|
166
|
+
if (elapsedMs + delay >= deadlineMs)
|
|
167
|
+
return { status, attempts: attempt, elapsedMs, ready: false };
|
|
168
|
+
await sleep(delay);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Total size from a ranged response's `content-range` (`bytes 0-0/12345`), else `content-length`
|
|
172
|
+
* when the gateway ignored the range and answered 200 with the whole thing. */
|
|
173
|
+
function totalBytesFrom(contentRange, contentLength, status) {
|
|
174
|
+
const m = contentRange?.match(/\/\s*(\d+)\s*$/);
|
|
175
|
+
if (m)
|
|
176
|
+
return Number(m[1]);
|
|
177
|
+
if (status === 200 && contentLength && /^\d+$/.test(contentLength))
|
|
178
|
+
return Number(contentLength);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
function safeHost(url) {
|
|
182
|
+
try {
|
|
183
|
+
return new URL(url).host;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return url;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=readiness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ResolveStorageOptions } from './resolve.js';
|
|
2
|
+
import { type ArweaveConfig } from './arweave.js';
|
|
3
|
+
/**
|
|
4
|
+
* Deploy-time storage readiness — what a REAL upload needs, decided from local facts (and, for the
|
|
5
|
+
* best-effort USD estimate, one price-API call) so "dry-run passes, then the real deploy fails at
|
|
6
|
+
* upload" can't happen. Three questions, each pure aside from the one noted network call:
|
|
7
|
+
*
|
|
8
|
+
* - {@link planTurboUpload} — is THIS ONE file free (Turbo's 100 KiB tier) or chargeable?
|
|
9
|
+
* - {@link assessStorageReadiness} — across every backend, what does a real upload of `sizes` need
|
|
10
|
+
* (Arweave credits, a Pinata JWT, a cloud public base)?
|
|
11
|
+
* - {@link assessTurboFunds} — does the resolved Turbo identity have ENOUGH prepaid credits for
|
|
12
|
+
* `sizes`, and (when it's short) does the creator's own wallet already hold some?
|
|
13
|
+
*
|
|
14
|
+
* None of this mints or spends anything — it only INTERROGATES the resolved provider. Identity
|
|
15
|
+
* creation is the caller's job (the CLI's managed-key flow owns the key file location; see
|
|
16
|
+
* `arweave-identity.ts` / `ArweaveConfig.ensureJwk`), so `assessTurboFunds` expects a config that
|
|
17
|
+
* already has one.
|
|
18
|
+
*/
|
|
19
|
+
/** Whether `opts` resolves to Arweave via the Turbo (prepaid-credits) provider — the only provider
|
|
20
|
+
* with a free tier and a funds balance to check; `http-bundler` skips every readiness/funds
|
|
21
|
+
* question below (it has its own auth, not a balance this package knows how to read). */
|
|
22
|
+
export declare function isTurboArweave(opts: ResolveStorageOptions): boolean;
|
|
23
|
+
export interface TurboUploadPlan {
|
|
24
|
+
/** Whether this upload is over Turbo's 100 KiB free tier (so it draws on prepaid credits). */
|
|
25
|
+
chargeable: boolean;
|
|
26
|
+
/** `size` in KB, formatted to 1 decimal — for the free-vs-credit readout. */
|
|
27
|
+
sizeKb: string;
|
|
28
|
+
}
|
|
29
|
+
/** The free-vs-credit decision for ONE Turbo upload of `size` bytes — from the local byte size
|
|
30
|
+
* alone, so it works under `--dry-run` too. Null when `opts` doesn't resolve to Turbo-Arweave
|
|
31
|
+
* (nothing to decide). */
|
|
32
|
+
export declare function planTurboUpload(opts: ResolveStorageOptions, size: number): TurboUploadPlan | null;
|
|
33
|
+
/** Per-backend readiness facts for a real upload of `sizes` (per-file byte counts). Each variant
|
|
34
|
+
* carries only what its backend needs checked; a backend with nothing to check (`fs`, or anything
|
|
35
|
+
* this package doesn't know about) reports `'other'` — a literal, not the resolved id, so the
|
|
36
|
+
* union stays a real discriminated union (a plain `string` member would swallow the other
|
|
37
|
+
* branches on narrowing) — callers that branch on `backend` never needed the id anyway. */
|
|
38
|
+
export type StorageReadinessReport = {
|
|
39
|
+
backend: 'arweave';
|
|
40
|
+
overCount: number;
|
|
41
|
+
totalCount: number;
|
|
42
|
+
totalKb: number;
|
|
43
|
+
usd: number | null;
|
|
44
|
+
} | {
|
|
45
|
+
backend: 'ipfs';
|
|
46
|
+
pinataMissingJwt: boolean;
|
|
47
|
+
} | {
|
|
48
|
+
backend: 'cloud';
|
|
49
|
+
missingPublicBase: boolean;
|
|
50
|
+
} | {
|
|
51
|
+
backend: 'other';
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* What a REAL deploy against `opts` needs, from `sizes` (per-file byte sizes) alone — Arweave
|
|
55
|
+
* credit needs (plus a best-effort USD estimate for the chargeable bytes), a missing Pinata JWT, or
|
|
56
|
+
* a missing cloud public base. The one network call (Turbo's price API) is best-effort: a failure
|
|
57
|
+
* there degrades `usd` to `null` rather than failing the whole check.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assessStorageReadiness(opts: ResolveStorageOptions, sizes: number[]): Promise<StorageReadinessReport>;
|
|
60
|
+
export interface TurboFundsCheck {
|
|
61
|
+
/** True when the resolved identity does NOT have enough prepaid credits for `sizes`. */
|
|
62
|
+
short: boolean;
|
|
63
|
+
address: string;
|
|
64
|
+
/** Whether the identity is an EVM one (`.env` key or a remote wallet) rather than the CLI-managed
|
|
65
|
+
* Arweave key — decides which recovery message applies. */
|
|
66
|
+
isEth: boolean;
|
|
67
|
+
haveWinc: number;
|
|
68
|
+
needWinc: number;
|
|
69
|
+
/** How many of `sizes` are over the free tier (what this shortfall is actually for). */
|
|
70
|
+
chargeableCount: number;
|
|
71
|
+
/** Set only when checked (short, a non-eth identity, and a wallet address was given): the
|
|
72
|
+
* creator's OWN wallet's Turbo balance, queryable by address with no key — so "spend those
|
|
73
|
+
* instead of topping up again" can be recommended before ever proposing a top-up. Null if that
|
|
74
|
+
* wallet has never had a Turbo balance. */
|
|
75
|
+
walletCredits?: {
|
|
76
|
+
winc: number;
|
|
77
|
+
credits: string;
|
|
78
|
+
} | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The pre-upload funds decision for a Turbo (Arweave) upload of `sizes` (per-file byte sizes):
|
|
82
|
+
* chargeable bytes → the Winston cost → compare against the identity's prepaid balance. `cfg` must
|
|
83
|
+
* already have a resolved identity (`jwk`/`ethSignerKey`/`remoteEth`) — this module only
|
|
84
|
+
* INTERROGATES the resolved provider, never mints one (see the module doc). Returns `null` when
|
|
85
|
+
* nothing is chargeable (every file is free) or the provider can't be reached right now — a
|
|
86
|
+
* best-effort check never false-blocks; the upload itself will surface any real error.
|
|
87
|
+
*/
|
|
88
|
+
export declare function assessTurboFunds(cfg: ArweaveConfig, sizes: number[], walletAddr?: string): Promise<TurboFundsCheck | null>;
|
|
89
|
+
//# sourceMappingURL=upload-readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-readiness.d.ts","sourceRoot":"","sources":["../src/upload-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,cAAc,CAAC;AACxD,OAAO,EAAyG,KAAK,aAAa,EAAC,MAAM,cAAc,CAAC;AAExJ;;;;;;;;;;;;;;;GAeG;AAEH;;0FAE0F;AAC1F,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAEnE;AAED,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,UAAU,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;2BAE2B;AAC3B,wBAAgB,eAAe,CAAC,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAGjG;AAED;;;;4FAI4F;AAC5F,MAAM,MAAM,sBAAsB,GAC9B;IAAC,OAAO,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,GAChG;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAA;CAAC,GAC5C;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAAC,GAC9C;IAAC,OAAO,EAAE,OAAO,CAAA;CAAC,CAAC;AAEvB;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAc1H;AAED,MAAM,WAAW,eAAe;IAC9B,wFAAwF;IACxF,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;gEAC4D;IAC5D,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,eAAe,EAAE,MAAM,CAAC;IACxB;;;gDAG4C;IAC5C,aAAa,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAuBhI"}
|