@needmoretruth/nmts-sdk 0.1.1
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/AGENTS.md +176 -0
- package/LICENSE +202 -0
- package/LICENSING.md +45 -0
- package/README.md +241 -0
- package/dist/env.d.ts +15 -0
- package/dist/env.js +42 -0
- package/dist/get.d.ts +35 -0
- package/dist/get.js +116 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +13 -0
- package/dist/list.d.ts +39 -0
- package/dist/list.js +35 -0
- package/dist/nmts.d.ts +103 -0
- package/dist/nmts.js +157 -0
- package/dist/product.d.ts +11 -0
- package/dist/product.js +12 -0
- package/dist/put-wallet.d.ts +18 -0
- package/dist/put-wallet.js +116 -0
- package/dist/put.d.ts +151 -0
- package/dist/put.js +168 -0
- package/dist/root.d.ts +67 -0
- package/dist/root.js +76 -0
- package/dist/session.d.ts +54 -0
- package/dist/session.js +74 -0
- package/dist/wallets.d.ts +63 -0
- package/dist/wallets.js +129 -0
- package/examples/quickstart.mjs +16 -0
- package/package.json +62 -0
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Credentials } from "./root.ts";
|
|
2
|
+
/** Where each credential was found. Reported, never the value. */
|
|
3
|
+
export type FoundIn = "file" | "variable";
|
|
4
|
+
export interface FromEnvironment {
|
|
5
|
+
credentials: Credentials;
|
|
6
|
+
accountCodeFrom: FoundIn;
|
|
7
|
+
apiKeyFrom: FoundIn;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The two credentials from the environment, or a refusal naming the variable that was missing.
|
|
11
|
+
*
|
|
12
|
+
* ⛔ THE REFUSAL NEVER CONTAINS A VALUE. It names variables; the caller's log is not a place a
|
|
13
|
+
* credential should be able to reach.
|
|
14
|
+
*/
|
|
15
|
+
export declare function credentialsFromEnvironment(): FromEnvironment;
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// The one place this package reads the environment — and only when `Nmts.fromEnv()` asks.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ THE SAME VARIABLE NAMES AS THE COMMAND-LINE TOOL, in the same order of preference, so a
|
|
4
|
+
// machine set up for `nmts` is set up for this package, and a container recipe written for one
|
|
5
|
+
// works for the other. A file named by `*_FILE` wins over a value in the variable itself,
|
|
6
|
+
// because a variable holding a path shows a reader a filename and a variable holding the value
|
|
7
|
+
// shows them the value (`docker inspect` prints the whole environment).
|
|
8
|
+
//
|
|
9
|
+
// ⚠ THE COMMAND-LINE TOOL STOPS ONCE FOR AN AGREEMENT before it will read the account code out of
|
|
10
|
+
// `NMTS_ACCOUNT_CODE`; this package does not, because it cannot: there is no terminal and no
|
|
11
|
+
// person on the other end of a library call. Choosing `fromEnv()` is that agreement. The README
|
|
12
|
+
// says so where the variables are explained, and says why the file form is the better one.
|
|
13
|
+
import { API_KEY_ENV_VAR, API_KEY_FILE_ENV_VAR, CODE_ENV_VAR, CODE_FILE_ENV_VAR, NmtsError, readSecretFile, } from "@needmoretruth/nmts-cli";
|
|
14
|
+
function fromFileOrVariable(fileVariable, valueVariable, what) {
|
|
15
|
+
const fromFile = readSecretFile(fileVariable);
|
|
16
|
+
if (fromFile !== null)
|
|
17
|
+
return { value: fromFile, from: "file" };
|
|
18
|
+
const fromVariable = process.env[valueVariable];
|
|
19
|
+
if (fromVariable !== undefined && fromVariable.length > 0) {
|
|
20
|
+
return { value: fromVariable, from: "variable" };
|
|
21
|
+
}
|
|
22
|
+
throw new NmtsError(`No ${what} in the environment.`, {
|
|
23
|
+
exitCode: 3,
|
|
24
|
+
nextStep: `Set ${fileVariable} to a file holding it (preferred), or ${valueVariable} to the value. ` +
|
|
25
|
+
`Or pass it to the constructor and do not use fromEnv().`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The two credentials from the environment, or a refusal naming the variable that was missing.
|
|
30
|
+
*
|
|
31
|
+
* ⛔ THE REFUSAL NEVER CONTAINS A VALUE. It names variables; the caller's log is not a place a
|
|
32
|
+
* credential should be able to reach.
|
|
33
|
+
*/
|
|
34
|
+
export function credentialsFromEnvironment() {
|
|
35
|
+
const code = fromFileOrVariable(CODE_FILE_ENV_VAR, CODE_ENV_VAR, "account code");
|
|
36
|
+
const key = fromFileOrVariable(API_KEY_FILE_ENV_VAR, API_KEY_ENV_VAR, "API key");
|
|
37
|
+
return {
|
|
38
|
+
credentials: { accountCode: code.value, apiKey: key.value },
|
|
39
|
+
accountCodeFrom: code.from,
|
|
40
|
+
apiKeyFrom: key.from,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/get.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type PlaintextSink, type ReadOptions } from "@needmoretruth/nmts-cli";
|
|
2
|
+
import { type Held, type Opened } from "./session.ts";
|
|
3
|
+
/** How big a file `get()` will hold in memory unless told otherwise: 256 MiB. */
|
|
4
|
+
export declare const DEFAULT_IN_MEMORY_LIMIT: number;
|
|
5
|
+
export interface GetResult {
|
|
6
|
+
/** The path as it was looked up. */
|
|
7
|
+
path: string;
|
|
8
|
+
/** How many plaintext bytes were delivered — the file's real length. */
|
|
9
|
+
bytes: number;
|
|
10
|
+
/** How many stored objects it came from. */
|
|
11
|
+
parts: number;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the account had sealed a whole-file hash and it matched.
|
|
14
|
+
*
|
|
15
|
+
* ⚠ False is not a failure: it means there was nothing to check against. Every part still
|
|
16
|
+
* opened under this account's key.
|
|
17
|
+
*/
|
|
18
|
+
contentHashChecked: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A sink that keeps the file in memory and hands it over once, after it is proved.
|
|
22
|
+
*
|
|
23
|
+
* ⛔ IT COPIES. The caller of `write` zeroes each run of plaintext as soon as the call resolves,
|
|
24
|
+
* so a sink that kept the reference would hold zeroes.
|
|
25
|
+
*/
|
|
26
|
+
export declare function bytesSink(limit: number): {
|
|
27
|
+
sink: PlaintextSink;
|
|
28
|
+
take(): Uint8Array;
|
|
29
|
+
};
|
|
30
|
+
/** Find the file the path names and fetch it into the sink. The sink decides what "delivered" means. */
|
|
31
|
+
export declare function fetchInto(held: Held, path: string, sink: PlaintextSink, read: ReadOptions | undefined): Promise<GetResult>;
|
|
32
|
+
/** The whole file, in memory, or a refusal. */
|
|
33
|
+
export declare function getBytes(opened: Opened, path: string, limit: number, read: ReadOptions | undefined): Promise<Uint8Array>;
|
|
34
|
+
/** The file written to `destination`, through a temporary name, visible only once it is proved. */
|
|
35
|
+
export declare function getToFile(opened: Opened, path: string, destination: string, force: boolean, read: ReadOptions | undefined): Promise<GetResult>;
|
package/dist/get.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// One file back out: fetched from the storage network, opened here, checked, then handed over.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ IT REFUSES RATHER THAN RETURNS A HALF-RIGHT FILE. A wrong key, a part that will not open,
|
|
4
|
+
// parts that do not add up, a whole-file hash that does not match — none of them produce
|
|
5
|
+
// bytes. That promise is kept by the sink: the bytes are delivered only on `commit`, after the
|
|
6
|
+
// whole file has been checked, and `abandon` leaves nothing behind. The command-line tool keeps
|
|
7
|
+
// the same promise with a temporary file and a rename; `fileSink` here is that same code.
|
|
8
|
+
//
|
|
9
|
+
// ⛔ THE IN-MEMORY FORM HAS A CEILING AND SAYS SO. `get()` returns a `Uint8Array`, which means the
|
|
10
|
+
// whole file is in this process at once. A file that would not fit is refused before a byte is
|
|
11
|
+
// fetched, and the refusal names `getTo()`, which streams to disk and has no ceiling.
|
|
12
|
+
import { entryAt, fetchFile, fileSink, KIND_FILE, NmtsError, } from "@needmoretruth/nmts-cli";
|
|
13
|
+
import { readList } from "./list.js";
|
|
14
|
+
import { withAccount } from "./session.js";
|
|
15
|
+
/** How big a file `get()` will hold in memory unless told otherwise: 256 MiB. */
|
|
16
|
+
export const DEFAULT_IN_MEMORY_LIMIT = 256 * 2 ** 20;
|
|
17
|
+
/**
|
|
18
|
+
* A sink that keeps the file in memory and hands it over once, after it is proved.
|
|
19
|
+
*
|
|
20
|
+
* ⛔ IT COPIES. The caller of `write` zeroes each run of plaintext as soon as the call resolves,
|
|
21
|
+
* so a sink that kept the reference would hold zeroes.
|
|
22
|
+
*/
|
|
23
|
+
export function bytesSink(limit) {
|
|
24
|
+
let buffer = null;
|
|
25
|
+
let at = 0;
|
|
26
|
+
let proved = false;
|
|
27
|
+
return {
|
|
28
|
+
sink: {
|
|
29
|
+
expect(size) {
|
|
30
|
+
if (size > limit) {
|
|
31
|
+
throw new NmtsError(`This file is ${size} bytes, over the ${limit}-byte limit for holding it in memory.`, {
|
|
32
|
+
exitCode: 4,
|
|
33
|
+
nextStep: "Nothing was fetched. Use getTo(path, destination) to stream it to a file, or raise `maxBytes`.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
buffer = new Uint8Array(size);
|
|
37
|
+
at = 0;
|
|
38
|
+
},
|
|
39
|
+
async write(bytes) {
|
|
40
|
+
if (buffer === null)
|
|
41
|
+
throw new NmtsError("The sink was written before it was told the size.");
|
|
42
|
+
if (at + bytes.length > buffer.length) {
|
|
43
|
+
throw new NmtsError(`The file produced more bytes (${at + bytes.length}) than its list entry says (${buffer.length}).`);
|
|
44
|
+
}
|
|
45
|
+
buffer.set(bytes, at);
|
|
46
|
+
at += bytes.length;
|
|
47
|
+
},
|
|
48
|
+
async commit() {
|
|
49
|
+
proved = true;
|
|
50
|
+
return true;
|
|
51
|
+
},
|
|
52
|
+
async abandon() {
|
|
53
|
+
buffer?.fill(0);
|
|
54
|
+
buffer = null;
|
|
55
|
+
at = 0;
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
take() {
|
|
59
|
+
if (!proved || buffer === null)
|
|
60
|
+
throw new NmtsError("The file was not proved, so there is nothing to hand over.");
|
|
61
|
+
const out = buffer;
|
|
62
|
+
buffer = null;
|
|
63
|
+
return out;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Find the file the path names and fetch it into the sink. The sink decides what "delivered" means. */
|
|
68
|
+
export async function fetchInto(held, path, sink, read) {
|
|
69
|
+
const { entries } = await readList(held);
|
|
70
|
+
if (entries.length === 0) {
|
|
71
|
+
throw new NmtsError("This account has no file list, so there is nothing to get.", { exitCode: 4 });
|
|
72
|
+
}
|
|
73
|
+
const entry = entryAt(entries, path, { nothingHappened: "Nothing was fetched." });
|
|
74
|
+
if (entry.kind !== KIND_FILE) {
|
|
75
|
+
throw new NmtsError(`"${path}" is a folder.`, {
|
|
76
|
+
exitCode: 4,
|
|
77
|
+
nextStep: "Nothing was fetched. get() takes one file at a time; list() shows what is in the folder.",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (entry.dekWrapped === undefined) {
|
|
81
|
+
throw new NmtsError(`The file list holds no key for "${path}".`, {
|
|
82
|
+
exitCode: 4,
|
|
83
|
+
nextStep: "Without it nothing can open the stored bytes. Open the account in a browser.",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const fetched = await fetchFile({
|
|
87
|
+
base: held.server,
|
|
88
|
+
apiKey: held.apiKey,
|
|
89
|
+
accountCode: held.code,
|
|
90
|
+
itemId: entry.id,
|
|
91
|
+
size: entry.size,
|
|
92
|
+
dekWrapped: entry.dekWrapped,
|
|
93
|
+
contentHashCt: entry.contentHashCt,
|
|
94
|
+
chain: held.network,
|
|
95
|
+
sink,
|
|
96
|
+
...(read === undefined ? {} : { read }),
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
path,
|
|
100
|
+
bytes: fetched.byteCount,
|
|
101
|
+
parts: fetched.partCount,
|
|
102
|
+
contentHashChecked: fetched.contentHashChecked,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** The whole file, in memory, or a refusal. */
|
|
106
|
+
export async function getBytes(opened, path, limit, read) {
|
|
107
|
+
return withAccount(opened, async (held) => {
|
|
108
|
+
const memory = bytesSink(limit);
|
|
109
|
+
await fetchInto(held, path, memory.sink, read);
|
|
110
|
+
return memory.take();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** The file written to `destination`, through a temporary name, visible only once it is proved. */
|
|
114
|
+
export async function getToFile(opened, path, destination, force, read) {
|
|
115
|
+
return withAccount(opened, async (held) => fetchInto(held, path, fileSink(destination, { force }), read));
|
|
116
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Nmts } from "./nmts.ts";
|
|
2
|
+
export type { AccountInfo, GetOptions, GetToOptions, NmtsOptions, WalletAddressOptions, } from "./nmts.ts";
|
|
3
|
+
export { deviceRoot, managedRoot } from "./root.ts";
|
|
4
|
+
export type { Credentials, Identity, ManagedCredentials, Root, RootMode } from "./root.ts";
|
|
5
|
+
export type { Entry } from "./list.ts";
|
|
6
|
+
export type { ActiveWallet, WalletInfo } from "./wallets.ts";
|
|
7
|
+
export type { CreditsPut, CreditsReview, PutOptions, PutResult, PutReview, PutStorage, WalletPut, WalletReview, } from "./put.ts";
|
|
8
|
+
export type { GetResult } from "./get.ts";
|
|
9
|
+
export { NmtsError, ServerError, UploadError } from "@needmoretruth/nmts-cli";
|
|
10
|
+
export type { FileUploadStep, ServerRefusal } from "@needmoretruth/nmts-cli";
|
|
11
|
+
export { HOME_URL, SDK_NAME, SOURCE_URL, VERSION } from "./product.ts";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// `@needmoretruth/nmts-sdk` — what `import { Nmts } from "@needmoretruth/nmts-sdk"` hands you.
|
|
2
|
+
//
|
|
3
|
+
// One class, three verbs, and the two answers to "who holds the key". Everything else is a type, or
|
|
4
|
+
// the error every failure arrives in.
|
|
5
|
+
export { Nmts } from "./nmts.js";
|
|
6
|
+
// Who holds the account's key. `Nmts.device()` and `Nmts.managed()` make these for you; the makers
|
|
7
|
+
// are here for a program that builds its client from a root it was handed.
|
|
8
|
+
export { deviceRoot, managedRoot } from "./root.js";
|
|
9
|
+
// The failures. `NmtsError` carries `exitCode` and `nextStep` exactly as the command-line tool's
|
|
10
|
+
// do, so a program can print the same sentence a person would have seen; `ServerError` is the
|
|
11
|
+
// server's own refusal with its code; `UploadError` says whether money moved.
|
|
12
|
+
export { NmtsError, ServerError, UploadError } from "@needmoretruth/nmts-cli";
|
|
13
|
+
export { HOME_URL, SDK_NAME, SOURCE_URL, VERSION } from "./product.js";
|
package/dist/list.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ManifestEntry, type PaddingRule } from "@needmoretruth/nmts-cli";
|
|
2
|
+
import { type Held, type Opened } from "./session.ts";
|
|
3
|
+
/** One thing in the account. */
|
|
4
|
+
export interface Entry {
|
|
5
|
+
/** The server's id for a file; a client-made id for a folder. Stable for the entry's life. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Full path from the top of the account, `photos/2026/cat.jpg`. Folders end without a slash. */
|
|
8
|
+
path: string;
|
|
9
|
+
kind: "file" | "folder";
|
|
10
|
+
/** Plaintext bytes. 0 for a folder. */
|
|
11
|
+
size: number;
|
|
12
|
+
/** ISO 8601, UTC. */
|
|
13
|
+
createdAt: string;
|
|
14
|
+
/** ISO 8601, UTC. */
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
}
|
|
17
|
+
/** The opened list: its entries, and the settings an upload needs from it. */
|
|
18
|
+
export interface OpenedList {
|
|
19
|
+
/** Empty for a new account that has no list yet — not an error. */
|
|
20
|
+
entries: readonly ManifestEntry[];
|
|
21
|
+
/**
|
|
22
|
+
* How coarsely a file's last part is rounded before sealing — the account's choice, made in the
|
|
23
|
+
* browser. An upload that rounded differently would say which program made it.
|
|
24
|
+
*/
|
|
25
|
+
padding: PaddingRule;
|
|
26
|
+
/**
|
|
27
|
+
* Which of this key's wallets pays, by index — the account's own number.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ IT COMES OUT OF THIS READ AND NOT A SECOND ONE, the way the command-line tool takes it. A
|
|
30
|
+
* wallet-paid upload prices the wallet it is about to sign with, so the number that was priced
|
|
31
|
+
* and the number that signs have to come from one answer.
|
|
32
|
+
*/
|
|
33
|
+
activeWallet: number;
|
|
34
|
+
}
|
|
35
|
+
/** Read the sealed list and open it. */
|
|
36
|
+
export declare function readList(held: Held): Promise<OpenedList>;
|
|
37
|
+
/** Every live entry, as a path, sorted so a folder comes before what is in it. */
|
|
38
|
+
export declare function toEntries(entries: readonly ManifestEntry[]): Entry[];
|
|
39
|
+
export declare function listEntries(opened: Opened): Promise<Entry[]>;
|
package/dist/list.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The account's files and folders, as paths.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ THE LIST IS OPENED HERE, ON THIS MACHINE. The server holds it sealed and cannot read a name in
|
|
4
|
+
// it; the account code is what opens it, which is why `list()` needs the code and not just the
|
|
5
|
+
// key. What comes back is every live entry with its full path — the trash is left out, the same
|
|
6
|
+
// way `nmts ls` leaves it out unless asked.
|
|
7
|
+
import { activeWalletOf, buildIndex, fullPathOf, isLive, KIND_FOLDER, readFileList, } from "@needmoretruth/nmts-cli";
|
|
8
|
+
import { withAccount } from "./session.js";
|
|
9
|
+
/** Read the sealed list and open it. */
|
|
10
|
+
export async function readList(held) {
|
|
11
|
+
const list = await readFileList(held.server, held.apiKey, held.code, held.accountId);
|
|
12
|
+
return {
|
|
13
|
+
entries: list.manifest?.entries ?? [],
|
|
14
|
+
padding: list.manifest?.settings?.paddingMode === "pow2" ? "pow2" : "padme",
|
|
15
|
+
activeWallet: activeWalletOf(list.manifest?.settings),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Every live entry, as a path, sorted so a folder comes before what is in it. */
|
|
19
|
+
export function toEntries(entries) {
|
|
20
|
+
const index = buildIndex(entries);
|
|
21
|
+
return entries
|
|
22
|
+
.filter((e) => isLive(index, e))
|
|
23
|
+
.map((e) => ({
|
|
24
|
+
id: e.id,
|
|
25
|
+
path: fullPathOf(index, e),
|
|
26
|
+
kind: e.kind === KIND_FOLDER ? "folder" : "file",
|
|
27
|
+
size: e.size,
|
|
28
|
+
createdAt: new Date(e.createdAt).toISOString(),
|
|
29
|
+
updatedAt: new Date(e.updatedAt).toISOString(),
|
|
30
|
+
}))
|
|
31
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
32
|
+
}
|
|
33
|
+
export async function listEntries(opened) {
|
|
34
|
+
return withAccount(opened, async (held) => toEntries((await readList(held)).entries));
|
|
35
|
+
}
|
package/dist/nmts.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type Network } from "@needmoretruth/nmts-cli";
|
|
2
|
+
import { type GetResult } from "./get.ts";
|
|
3
|
+
import { type Entry } from "./list.ts";
|
|
4
|
+
import { type PutOptions, type PutResult, type PutReview } from "./put.ts";
|
|
5
|
+
import { type Credentials, type ManagedCredentials, type Root } from "./root.ts";
|
|
6
|
+
import { type ServerOptions } from "./session.ts";
|
|
7
|
+
import { type ActiveWallet, type WalletInfo } from "./wallets.ts";
|
|
8
|
+
export interface NmtsOptions extends ServerOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Hosts to read stored bytes from, instead of the public aggregators for the network. For a
|
|
11
|
+
* development stack, or an aggregator you run yourself.
|
|
12
|
+
*/
|
|
13
|
+
aggregators?: readonly string[] | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface GetOptions {
|
|
16
|
+
/** How many bytes `get()` may hold in memory. Default 256 MiB. Over it, use `getTo()`. */
|
|
17
|
+
maxBytes?: number | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface GetToOptions {
|
|
20
|
+
/** Replace a file already at the destination. Off by default, and saying so is the point. */
|
|
21
|
+
force?: boolean | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface WalletAddressOptions {
|
|
24
|
+
/** Which of this key's wallets. Absent = the one the account pays from. */
|
|
25
|
+
index?: number | undefined;
|
|
26
|
+
}
|
|
27
|
+
export interface AccountInfo {
|
|
28
|
+
/** The account's public id — what the server knows it by. Not a secret. */
|
|
29
|
+
accountId: string;
|
|
30
|
+
server: string;
|
|
31
|
+
network: Network;
|
|
32
|
+
}
|
|
33
|
+
export declare class Nmts {
|
|
34
|
+
#private;
|
|
35
|
+
constructor(root: Root, options?: NmtsOptions);
|
|
36
|
+
/** The key is in THIS process: a browser, an app, a game client, your own program. */
|
|
37
|
+
static device(credentials: Credentials & NmtsOptions): Nmts;
|
|
38
|
+
/**
|
|
39
|
+
* The key is in YOUR store, sealed, and `openCode` opens it for one call at a time.
|
|
40
|
+
*
|
|
41
|
+
* What this means for the people whose files they are is in the README's first table: a business
|
|
42
|
+
* that holds the key can read the files. It is a choice, not a defect, and it is the caller's.
|
|
43
|
+
*/
|
|
44
|
+
static managed(source: ManagedCredentials & NmtsOptions): Nmts;
|
|
45
|
+
/**
|
|
46
|
+
* A device client whose credentials come from the environment, the way the command-line tool
|
|
47
|
+
* finds them.
|
|
48
|
+
*
|
|
49
|
+
* `NMTS_ACCOUNT_CODE_FILE` and `NMTS_API_KEY_FILE` name files (preferred); `NMTS_ACCOUNT_CODE`
|
|
50
|
+
* and `NMTS_API_KEY` hold the values. `NMTS_SERVER` and `NMTS_NETWORK` are read by every call
|
|
51
|
+
* either way. Nothing is read until this is called.
|
|
52
|
+
*/
|
|
53
|
+
static fromEnv(options?: NmtsOptions): Nmts;
|
|
54
|
+
/** Which account this is, on which server and network. Offline: derived from the code alone. */
|
|
55
|
+
account(): Promise<AccountInfo>;
|
|
56
|
+
/**
|
|
57
|
+
* The Sui address of one of the wallets the account code derives — where a developer who pays
|
|
58
|
+
* for storage from their own coins sends them. Nothing is signed.
|
|
59
|
+
*
|
|
60
|
+
* With no argument it is the wallet the account PAYS FROM, read out of the sealed file list;
|
|
61
|
+
* when that cannot be read this throws rather than answering the first wallet, because coins
|
|
62
|
+
* sent to an address nobody chose are coins the account cannot spend. `{ index }` names one
|
|
63
|
+
* directly and is offline — numbers come from the key, so every one of them already exists.
|
|
64
|
+
*/
|
|
65
|
+
walletAddress(options?: WalletAddressOptions): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Every wallet this account has — the ones it has made, and any further out that a chain says
|
|
68
|
+
* have been used — with the one that pays marked. Nothing is spent and nothing is written.
|
|
69
|
+
*/
|
|
70
|
+
wallets(): Promise<WalletInfo[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Say which of this key's wallets pays for storage from now on.
|
|
73
|
+
*
|
|
74
|
+
* It is the ACCOUNT's choice and not this machine's: the number rides inside the sealed file
|
|
75
|
+
* list, so every device and every program on this account pays from the same address afterwards.
|
|
76
|
+
* Nothing is created or deleted — every number a key can derive already exists.
|
|
77
|
+
*/
|
|
78
|
+
setActiveWallet(index: number): Promise<ActiveWallet>;
|
|
79
|
+
/** Every live file and folder, as paths. Nothing is spent. */
|
|
80
|
+
list(): Promise<Entry[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Upload one file. **This spends.** By default it spends credits — one per started MiB of
|
|
83
|
+
* sealed bytes, for the storage period the account buys uploads for. With `pay: "wallet"` it
|
|
84
|
+
* spends WAL and SUI out of the wallet the account pays from instead — `wallet: n` names another
|
|
85
|
+
* of this key's wallets for this one upload — for as many of the storage network's epochs as
|
|
86
|
+
* `epochs` asks for. Neither comes back, and calling this is the agreement to that;
|
|
87
|
+
* `dryRun: true` says what it would cost and spends nothing.
|
|
88
|
+
*
|
|
89
|
+
* A path names a file on this machine; bytes need a `name`. A name already in use is numbered,
|
|
90
|
+
* `report (2).pdf`, unless the machine's `nmts on-collision` setting says overwrite.
|
|
91
|
+
*/
|
|
92
|
+
put(file: string | Uint8Array, options: PutOptions & {
|
|
93
|
+
dryRun: true;
|
|
94
|
+
}): Promise<PutReview>;
|
|
95
|
+
put(file: string | Uint8Array, options?: PutOptions): Promise<PutResult>;
|
|
96
|
+
/** One file, whole and checked, in memory. Nothing is spent. */
|
|
97
|
+
get(path: string, options?: GetOptions): Promise<Uint8Array>;
|
|
98
|
+
/**
|
|
99
|
+
* One file, streamed to `destination` and made visible only once it is checked. No size
|
|
100
|
+
* ceiling. Refuses to overwrite unless `force` says so.
|
|
101
|
+
*/
|
|
102
|
+
getTo(path: string, destination: string, options?: GetToOptions): Promise<GetResult>;
|
|
103
|
+
}
|
package/dist/nmts.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// `Nmts` — one account, opened once, with the three things a program does with storage.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ THE ROOT IS THE FIRST ARGUMENT BECAUSE IT IS THE ONE DECISION THAT MATTERS. Who holds the
|
|
4
|
+
// account's key — this process, or a business's sealed store — is answered once, where the
|
|
5
|
+
// client is made, and every method below reads the same whichever answer it was. That is the
|
|
6
|
+
// whole of how this package keeps the rule that a verb must work the same for both; the two
|
|
7
|
+
// convenience makers are named after the two answers so the choice cannot be made by accident.
|
|
8
|
+
//
|
|
9
|
+
// ⛔ THE CONSTRUCTOR DOES NO WORK. It keeps the root and the options and nothing else happens
|
|
10
|
+
// until the first call, so making one is free, making one with a bad code fails on the first
|
|
11
|
+
// call rather than in a constructor that cannot be awaited, and a server that is down is a
|
|
12
|
+
// failure of the call that needed it.
|
|
13
|
+
//
|
|
14
|
+
// ⛔ EVERY METHOD IS THE COMMAND-LINE TOOL'S CODE with the terminal taken off. `list` is `nmts ls`,
|
|
15
|
+
// `put` is `nmts put`, `get` is `nmts get`. Where this file decides something the tool decides
|
|
16
|
+
// by asking a person — spending, reading a code from the environment — the decision is the
|
|
17
|
+
// caller's, made by calling the method, and the README says so at the top.
|
|
18
|
+
import { createBlobProtocol, createUploadApi, fileSource, measureLocal, readCurrentEpoch, walletAddress, } from "@needmoretruth/nmts-cli";
|
|
19
|
+
import { credentialsFromEnvironment } from "./env.js";
|
|
20
|
+
import { DEFAULT_IN_MEMORY_LIMIT, getBytes, getToFile } from "./get.js";
|
|
21
|
+
import { listEntries } from "./list.js";
|
|
22
|
+
import { bytesSource, nameOf, putSource, } from "./put.js";
|
|
23
|
+
import { putSourceWithWallet } from "./put-wallet.js";
|
|
24
|
+
import { deviceRoot, managedRoot } from "./root.js";
|
|
25
|
+
import { openAccount, withAccount } from "./session.js";
|
|
26
|
+
import { payingWallet, requireWalletIndex, setActiveWallet, wallets, } from "./wallets.js";
|
|
27
|
+
/**
|
|
28
|
+
* The three fields that say where to talk, taken off a convenience maker's one object.
|
|
29
|
+
*
|
|
30
|
+
* ⛔ SO THAT NO CREDENTIAL IS COPIED ONTO THE CLIENT. `Nmts.device({ accountCode, … })` takes one
|
|
31
|
+
* flat object because that is what is pleasant to write; what the client keeps out of it is
|
|
32
|
+
* these three, and the code goes to the root and nowhere else.
|
|
33
|
+
*/
|
|
34
|
+
function optionsOf(from) {
|
|
35
|
+
return { server: from.server, network: from.network, aggregators: from.aggregators };
|
|
36
|
+
}
|
|
37
|
+
export class Nmts {
|
|
38
|
+
#root;
|
|
39
|
+
#options;
|
|
40
|
+
#opened = null;
|
|
41
|
+
constructor(root, options = {}) {
|
|
42
|
+
this.#root = root;
|
|
43
|
+
this.#options = { ...options };
|
|
44
|
+
}
|
|
45
|
+
/** The key is in THIS process: a browser, an app, a game client, your own program. */
|
|
46
|
+
static device(credentials) {
|
|
47
|
+
return new Nmts(deviceRoot(credentials), optionsOf(credentials));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The key is in YOUR store, sealed, and `openCode` opens it for one call at a time.
|
|
51
|
+
*
|
|
52
|
+
* What this means for the people whose files they are is in the README's first table: a business
|
|
53
|
+
* that holds the key can read the files. It is a choice, not a defect, and it is the caller's.
|
|
54
|
+
*/
|
|
55
|
+
static managed(source) {
|
|
56
|
+
return new Nmts(managedRoot(source), optionsOf(source));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A device client whose credentials come from the environment, the way the command-line tool
|
|
60
|
+
* finds them.
|
|
61
|
+
*
|
|
62
|
+
* `NMTS_ACCOUNT_CODE_FILE` and `NMTS_API_KEY_FILE` name files (preferred); `NMTS_ACCOUNT_CODE`
|
|
63
|
+
* and `NMTS_API_KEY` hold the values. `NMTS_SERVER` and `NMTS_NETWORK` are read by every call
|
|
64
|
+
* either way. Nothing is read until this is called.
|
|
65
|
+
*/
|
|
66
|
+
static fromEnv(options = {}) {
|
|
67
|
+
const found = credentialsFromEnvironment();
|
|
68
|
+
return Nmts.device({ ...options, ...found.credentials });
|
|
69
|
+
}
|
|
70
|
+
// A failed open is not kept: the assignment never happens, so the next call tries again.
|
|
71
|
+
#account() {
|
|
72
|
+
this.#opened ??= openAccount(this.#root, this.#options);
|
|
73
|
+
return this.#opened;
|
|
74
|
+
}
|
|
75
|
+
#read() {
|
|
76
|
+
const hosts = this.#options.aggregators;
|
|
77
|
+
return hosts === undefined || hosts.length === 0 ? undefined : { hosts };
|
|
78
|
+
}
|
|
79
|
+
/** Which account this is, on which server and network. Offline: derived from the code alone. */
|
|
80
|
+
async account() {
|
|
81
|
+
return withAccount(this.#account(), async (held) => ({
|
|
82
|
+
accountId: held.accountId,
|
|
83
|
+
server: held.server,
|
|
84
|
+
network: held.network,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The Sui address of one of the wallets the account code derives — where a developer who pays
|
|
89
|
+
* for storage from their own coins sends them. Nothing is signed.
|
|
90
|
+
*
|
|
91
|
+
* With no argument it is the wallet the account PAYS FROM, read out of the sealed file list;
|
|
92
|
+
* when that cannot be read this throws rather than answering the first wallet, because coins
|
|
93
|
+
* sent to an address nobody chose are coins the account cannot spend. `{ index }` names one
|
|
94
|
+
* directly and is offline — numbers come from the key, so every one of them already exists.
|
|
95
|
+
*/
|
|
96
|
+
async walletAddress(options = {}) {
|
|
97
|
+
const opened = this.#account();
|
|
98
|
+
const asked = options.index;
|
|
99
|
+
if (asked !== undefined) {
|
|
100
|
+
const index = requireWalletIndex(asked);
|
|
101
|
+
return opened.root.withCode(async (code) => walletAddress(code, index));
|
|
102
|
+
}
|
|
103
|
+
return withAccount(opened, async (held) => walletAddress(held.code, await payingWallet(held)));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Every wallet this account has — the ones it has made, and any further out that a chain says
|
|
107
|
+
* have been used — with the one that pays marked. Nothing is spent and nothing is written.
|
|
108
|
+
*/
|
|
109
|
+
async wallets() {
|
|
110
|
+
return wallets(this.#account());
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Say which of this key's wallets pays for storage from now on.
|
|
114
|
+
*
|
|
115
|
+
* It is the ACCOUNT's choice and not this machine's: the number rides inside the sealed file
|
|
116
|
+
* list, so every device and every program on this account pays from the same address afterwards.
|
|
117
|
+
* Nothing is created or deleted — every number a key can derive already exists.
|
|
118
|
+
*/
|
|
119
|
+
async setActiveWallet(index) {
|
|
120
|
+
return setActiveWallet(this.#account(), index);
|
|
121
|
+
}
|
|
122
|
+
/** Every live file and folder, as paths. Nothing is spent. */
|
|
123
|
+
async list() {
|
|
124
|
+
return listEntries(this.#account());
|
|
125
|
+
}
|
|
126
|
+
async put(file, options = {}) {
|
|
127
|
+
const opened = this.#account();
|
|
128
|
+
const source = typeof file === "string" ? fileSource(file, measureLocal(file)) : bytesSource(file);
|
|
129
|
+
const name = options.name ?? (typeof file === "string" ? nameOf(file) : "");
|
|
130
|
+
// ⛔ WHICH MONEY IS DECIDED BEFORE ANYTHING IS READ, as the command-line tool decides it. The
|
|
131
|
+
// credit rail below cannot price in WAL or sign a transaction, and it must not learn.
|
|
132
|
+
if (options.pay === "wallet")
|
|
133
|
+
return putSourceWithWallet(opened, source, name, options);
|
|
134
|
+
return putSource(opened, source, name, options, (sealedBytes) => this.#rail(opened, sealedBytes, options));
|
|
135
|
+
}
|
|
136
|
+
/** The credit-paid rail: the server sells the storage, the network's relay takes the bytes. */
|
|
137
|
+
async #rail(opened, sealedBytes, options) {
|
|
138
|
+
const protocol = createBlobProtocol(opened.network, sealedBytes, options.onProgress);
|
|
139
|
+
return {
|
|
140
|
+
api: createUploadApi(opened.server, opened.apiKey),
|
|
141
|
+
protocol,
|
|
142
|
+
relayUrl: protocol.relayUrl,
|
|
143
|
+
currentEpoch: await readCurrentEpoch(opened.network),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** One file, whole and checked, in memory. Nothing is spent. */
|
|
147
|
+
async get(path, options = {}) {
|
|
148
|
+
return getBytes(this.#account(), path, options.maxBytes ?? DEFAULT_IN_MEMORY_LIMIT, this.#read());
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* One file, streamed to `destination` and made visible only once it is checked. No size
|
|
152
|
+
* ceiling. Refuses to overwrite unless `force` says so.
|
|
153
|
+
*/
|
|
154
|
+
async getTo(path, destination, options = {}) {
|
|
155
|
+
return getToFile(this.#account(), path, destination, options.force === true, this.#read());
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const SDK_NAME = "@needmoretruth/nmts-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* The version this code says it is.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ KEPT IN STEP WITH `package.json` BY HAND, for the same reason the command-line tool keeps two
|
|
6
|
+
* copies: an installed package cannot read its own manifest from every place it may be loaded,
|
|
7
|
+
* and a program that reports the wrong version makes every defect report about it untrustworthy.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VERSION = "0.1.1";
|
|
10
|
+
export declare const HOME_URL = "https://nmts.me";
|
|
11
|
+
export declare const SOURCE_URL = "https://github.com/needmoretruth/nmts-sdk";
|
package/dist/product.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// What this package is. Pure constants, so anything may import them without loading the rest.
|
|
2
|
+
export const SDK_NAME = "@needmoretruth/nmts-sdk";
|
|
3
|
+
/**
|
|
4
|
+
* The version this code says it is.
|
|
5
|
+
*
|
|
6
|
+
* ⛔ KEPT IN STEP WITH `package.json` BY HAND, for the same reason the command-line tool keeps two
|
|
7
|
+
* copies: an installed package cannot read its own manifest from every place it may be loaded,
|
|
8
|
+
* and a program that reports the wrong version makes every defect report about it untrustworthy.
|
|
9
|
+
*/
|
|
10
|
+
export const VERSION = "0.1.1";
|
|
11
|
+
export const HOME_URL = "https://nmts.me";
|
|
12
|
+
export const SOURCE_URL = "https://github.com/needmoretruth/nmts-sdk";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type PlaintextSource, type WalletPutSeams } from "@needmoretruth/nmts-cli";
|
|
2
|
+
import { type PutOptions, type WalletPut, type WalletReview } from "./put.ts";
|
|
3
|
+
import { type Opened } from "./session.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The chain, the signatures and the wire of this rail. ⚠ Seams, not options: no caller of `put()`
|
|
6
|
+
* reaches them, and a test hands in fakes exactly as it does for the credit rail.
|
|
7
|
+
*/
|
|
8
|
+
export type WalletSeams = Pick<WalletPutSeams, "readChain" | "sign" | "protocol" | "api">;
|
|
9
|
+
/**
|
|
10
|
+
* Seal, buy with the account's OWN WALLET, push and record one file.
|
|
11
|
+
*
|
|
12
|
+
* ⛔ NOBODY IS ASKED, BECAUSE THERE IS NOBODY TO ASK. The command-line tool prints the review and
|
|
13
|
+
* holds it against a standing wallet agreement; in a library the code that can spend is already
|
|
14
|
+
* in this process and the caller wrote the call, so calling this IS the agreement — exactly as
|
|
15
|
+
* the credit rail already says. What does not change is the order: the price, the balances and
|
|
16
|
+
* a shortfall all come before the first signature.
|
|
17
|
+
*/
|
|
18
|
+
export declare function putSourceWithWallet(opened: Opened, source: PlaintextSource, name: string, options: PutOptions, seams?: WalletSeams): Promise<WalletPut | WalletReview>;
|