wative-core 2.3.9 → 2.4.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/CHANGELOG.md +197 -0
- package/README.md +127 -91
- package/dist/artifacts/evm/index.cjs +1 -1
- package/dist/artifacts/evm/index.d.cts +2 -2
- package/dist/artifacts/evm/index.d.ts +2 -2
- package/dist/artifacts/evm/index.js +1 -1
- package/dist/artifacts/svm/index.cjs +1 -1
- package/dist/artifacts/svm/index.d.cts +3 -3
- package/dist/artifacts/svm/index.d.ts +3 -3
- package/dist/artifacts/svm/index.js +1 -1
- package/dist/{container-provider-D_557LVN.d.ts → container-provider--npoeR2N.d.ts} +1 -1
- package/dist/{container-provider-CkKGyHis.d.cts → container-provider-Ca_5z1uQ.d.cts} +1 -1
- package/dist/{evm-Xw4zBLUH.d.ts → evm-EHpaW_sK.d.ts} +1 -1
- package/dist/{evm-gsdoVj16.d.cts → evm-hSJJOVG3.d.cts} +1 -1
- package/dist/{file-sink-C_SlT9IC.d.cts → file-sink-7ZAutH45.d.cts} +2 -2
- package/dist/{file-sink-D33jvque.d.ts → file-sink-Bk5zYHw-.d.ts} +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +12 -8
- package/dist/index.d.ts +12 -8
- package/dist/index.js +1 -1
- package/dist/index.node.cjs +1 -1
- package/dist/index.node.d.cts +6 -6
- package/dist/index.node.d.ts +6 -6
- package/dist/index.node.js +1 -1
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +3 -3
- package/dist/node/index.d.ts +3 -3
- package/dist/node/index.js +1 -1
- package/dist/{svm-CUuqpl1h.d.ts → svm-B5q8YaV9.d.ts} +1 -1
- package/dist/{svm-CTCJsYri.d.cts → svm-CXFwmeMo.d.cts} +1 -1
- package/dist/{workspace-Du_jy51u.d.cts → workspace-kq3QyAgm.d.cts} +51 -10
- package/dist/{workspace-Du_jy51u.d.ts → workspace-kq3QyAgm.d.ts} +51 -10
- package/examples/15-package-resolution.test.cjs +4 -4
- package/examples/19-simple-provider.test.cjs +134 -0
- package/examples/README.md +10 -11
- package/package.json +1 -1
|
@@ -118,7 +118,7 @@ interface Settling<T> {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
type RecordType = "CONFIG" | "ACCOUNTS" | "NETWORK" | "ASSET" | "LOG";
|
|
121
|
-
|
|
121
|
+
/** Pre-unlock structural state of a provider's container — see `Provider.inspectContainer`. */
|
|
122
122
|
type ContainerState = "empty" | "workspace" | "foreign";
|
|
123
123
|
interface RecordInit<T> {
|
|
124
124
|
readonly provider: Provider;
|
|
@@ -128,7 +128,11 @@ interface RecordInit<T> {
|
|
|
128
128
|
readonly password?: string;
|
|
129
129
|
readonly decrypt?: (ciphertext: Uint8Array, password: string) => Uint8Array;
|
|
130
130
|
readonly persist?: (value: T, password: string) => Promise<void>;
|
|
131
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Throws if the session that issued this record has ended. Supplied by
|
|
133
|
+
* providers that HAVE a session; a provider without one omits it and the
|
|
134
|
+
* record behaves as before.
|
|
135
|
+
*/
|
|
132
136
|
readonly requireLiveSession?: () => void;
|
|
133
137
|
}
|
|
134
138
|
declare class Record$1<T> {
|
|
@@ -138,9 +142,19 @@ declare class Record$1<T> {
|
|
|
138
142
|
constructor(slug: Slug, path: string, init?: RecordInit<T>);
|
|
139
143
|
get locked(): boolean;
|
|
140
144
|
get value(): T;
|
|
141
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Pure CPU — decrypts the in-memory blob. Sync.
|
|
147
|
+
*
|
|
148
|
+
* BAD_PASSWORD means the password is wrong: an AES-GCM auth-tag mismatch on
|
|
149
|
+
* the locked path, or a mismatch against the password that opened the record
|
|
150
|
+
* on the unlocked one. DECRYPT_FAILED means the record cannot answer —
|
|
151
|
+
* structural damage or corruption on the locked path, and on the unlocked
|
|
152
|
+
* path a record carrying neither a password nor ciphertext, which has nothing
|
|
153
|
+
* to verify against. Callers can still read the two apart as "wrong password"
|
|
154
|
+
* versus "cannot be checked".
|
|
155
|
+
*/
|
|
142
156
|
unlock(password: string): this;
|
|
143
|
-
|
|
157
|
+
/** Persists changes via the owning Provider. */
|
|
144
158
|
save(): Promise<void>;
|
|
145
159
|
}
|
|
146
160
|
declare abstract class Provider {
|
|
@@ -156,20 +170,45 @@ declare abstract class Provider {
|
|
|
156
170
|
abstract unlockContainer(password: string): Promise<boolean>;
|
|
157
171
|
abstract lockContainer(): Promise<void>;
|
|
158
172
|
abstract isContainerUnlocked(): boolean;
|
|
159
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Structural inspection of the container BEFORE unlock. `Workspace.open()`
|
|
175
|
+
* uses it to auto-select create-vs-open without a `create` flag:
|
|
176
|
+
* - `"empty"` — no container yet; safe to initialize a new workspace.
|
|
177
|
+
* - `"workspace"` — an existing wative workspace; open it.
|
|
178
|
+
* - `"foreign"` — non-empty storage that is NOT a wative workspace; `open()`
|
|
179
|
+
* refuses rather than scaffolding a workspace into it.
|
|
180
|
+
* The base default returns `"workspace"` (proceed as open-or-create, no guard),
|
|
181
|
+
* so existing custom providers are unaffected. Providers that can cheaply
|
|
182
|
+
* inspect their storage — e.g. `HybridProvider` on a filesystem — override this
|
|
183
|
+
* to detect `"foreign"` and stop a mistyped path from creating a workspace
|
|
184
|
+
* inside unrelated data.
|
|
185
|
+
*/
|
|
160
186
|
inspectContainer(): Promise<ContainerState>;
|
|
161
187
|
abstract loadRecord<T>(type: RecordType, slug?: Slug): Promise<Record$1<T>>;
|
|
162
188
|
abstract loadRecords<T>(type: RecordType): Promise<ReadonlyArray<Record$1<T>>>;
|
|
163
189
|
abstract writeRecord<T>(type: RecordType, slug: Slug, value: T): Promise<void>;
|
|
164
190
|
abstract dropRecord(type: RecordType, slug: Slug): Promise<void>;
|
|
165
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Initialize an empty container by writing the four root records
|
|
193
|
+
* (CONFIG, NETWORK, ASSET, LOG) from the bundled template files at
|
|
194
|
+
* `src/workspace_template/`. Idempotent: existing records are left
|
|
195
|
+
* untouched. The ACCOUNTS namespace is left empty — per-account records
|
|
196
|
+
* are created lazily by `accounts.create(...)`.
|
|
197
|
+
*
|
|
198
|
+
* Subclasses inherit this default; override only if a backend needs
|
|
199
|
+
* non-record initialization (e.g. allocating a database schema).
|
|
200
|
+
*/
|
|
166
201
|
initialize(): Promise<void>;
|
|
167
|
-
|
|
202
|
+
/** Any storage at all under the accounts area, however named. Defaults to
|
|
203
|
+
* "nothing", so a provider that does not implement it can only fail to add a
|
|
204
|
+
* refusal reason — never cause one. */
|
|
168
205
|
protected _hasRawAccountData(): Promise<boolean>;
|
|
169
206
|
abstract close(): Promise<void>;
|
|
170
207
|
}
|
|
171
208
|
|
|
172
|
-
type WativeErrorCode = "WORKSPACE_LOCKED" | "ACCOUNT_LOCKED" | "RECORD_LOCKED" | "BAD_PASSWORD" | "WEAK_PASSWORD" | "RECORD_NOT_FOUND" | "PROVIDER_IO" | "PERMISSION_DENIED" | "DISK_FULL" | "PARAMETER_ERROR" | "DECRYPT_FAILED" | "ENCRYPT_FAILED" | "ALGORITHM_IRREVERSIBLE" | "INVALID_MNEMONIC" | "INVALID_PRIVATE_KEY" | "UNSUPPORTED_NETWORK" | "TX_BUILD_FAILED" | "TX_SIGN_FAILED" | "TX_SUBMIT_FAILED" | "TX_TIMEOUT" | "TX_DROPPED" | "TX_ABORTED" | "RPC_UNREACHABLE"
|
|
209
|
+
type WativeErrorCode = "WORKSPACE_LOCKED" | "ACCOUNT_LOCKED" | "RECORD_LOCKED" | "BAD_PASSWORD" | "WEAK_PASSWORD" | "RECORD_NOT_FOUND" | "PROVIDER_IO" | "PERMISSION_DENIED" | "DISK_FULL" | "PARAMETER_ERROR" | "DECRYPT_FAILED" | "ENCRYPT_FAILED" | "ALGORITHM_IRREVERSIBLE" | "INVALID_MNEMONIC" | "INVALID_PRIVATE_KEY" | "UNSUPPORTED_NETWORK" | "TX_BUILD_FAILED" | "TX_SIGN_FAILED" | "TX_SUBMIT_FAILED" | "TX_TIMEOUT" | "TX_DROPPED" | "TX_ABORTED" | "RPC_UNREACHABLE"
|
|
210
|
+
|
|
211
|
+
| "RPC_REJECTED" | "STORAGE_NOT_DURABLE" | "UNSUPPORTED_OP";
|
|
173
212
|
interface WativeErrorOpts {
|
|
174
213
|
readonly cause?: unknown;
|
|
175
214
|
readonly details?: Record<string, unknown>;
|
|
@@ -312,8 +351,8 @@ declare class EvmTransaction implements Transaction {
|
|
|
312
351
|
}>;
|
|
313
352
|
address?: Address;
|
|
314
353
|
});
|
|
315
|
-
constructor(from: EvmAddress, to: string, value: bigint, data
|
|
316
|
-
chainId
|
|
354
|
+
constructor(from: EvmAddress, to: string, value: bigint, data: string | undefined, opts: {
|
|
355
|
+
chainId: ChainId;
|
|
317
356
|
type?: EvmTxType;
|
|
318
357
|
nonce?: number;
|
|
319
358
|
gasPrice?: bigint;
|
|
@@ -714,6 +753,8 @@ declare class Workspace {
|
|
|
714
753
|
|
|
715
754
|
_registerLiveAccount(account: Account): void;
|
|
716
755
|
|
|
756
|
+
_releaseAccount(account: Account): void;
|
|
757
|
+
|
|
717
758
|
_enqueueNetworkMutation<T>(fn: () => Promise<T>): Promise<T>;
|
|
718
759
|
private constructor();
|
|
719
760
|
get locked(): boolean;
|
|
@@ -118,7 +118,7 @@ interface Settling<T> {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
type RecordType = "CONFIG" | "ACCOUNTS" | "NETWORK" | "ASSET" | "LOG";
|
|
121
|
-
|
|
121
|
+
/** Pre-unlock structural state of a provider's container — see `Provider.inspectContainer`. */
|
|
122
122
|
type ContainerState = "empty" | "workspace" | "foreign";
|
|
123
123
|
interface RecordInit<T> {
|
|
124
124
|
readonly provider: Provider;
|
|
@@ -128,7 +128,11 @@ interface RecordInit<T> {
|
|
|
128
128
|
readonly password?: string;
|
|
129
129
|
readonly decrypt?: (ciphertext: Uint8Array, password: string) => Uint8Array;
|
|
130
130
|
readonly persist?: (value: T, password: string) => Promise<void>;
|
|
131
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Throws if the session that issued this record has ended. Supplied by
|
|
133
|
+
* providers that HAVE a session; a provider without one omits it and the
|
|
134
|
+
* record behaves as before.
|
|
135
|
+
*/
|
|
132
136
|
readonly requireLiveSession?: () => void;
|
|
133
137
|
}
|
|
134
138
|
declare class Record$1<T> {
|
|
@@ -138,9 +142,19 @@ declare class Record$1<T> {
|
|
|
138
142
|
constructor(slug: Slug, path: string, init?: RecordInit<T>);
|
|
139
143
|
get locked(): boolean;
|
|
140
144
|
get value(): T;
|
|
141
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Pure CPU — decrypts the in-memory blob. Sync.
|
|
147
|
+
*
|
|
148
|
+
* BAD_PASSWORD means the password is wrong: an AES-GCM auth-tag mismatch on
|
|
149
|
+
* the locked path, or a mismatch against the password that opened the record
|
|
150
|
+
* on the unlocked one. DECRYPT_FAILED means the record cannot answer —
|
|
151
|
+
* structural damage or corruption on the locked path, and on the unlocked
|
|
152
|
+
* path a record carrying neither a password nor ciphertext, which has nothing
|
|
153
|
+
* to verify against. Callers can still read the two apart as "wrong password"
|
|
154
|
+
* versus "cannot be checked".
|
|
155
|
+
*/
|
|
142
156
|
unlock(password: string): this;
|
|
143
|
-
|
|
157
|
+
/** Persists changes via the owning Provider. */
|
|
144
158
|
save(): Promise<void>;
|
|
145
159
|
}
|
|
146
160
|
declare abstract class Provider {
|
|
@@ -156,20 +170,45 @@ declare abstract class Provider {
|
|
|
156
170
|
abstract unlockContainer(password: string): Promise<boolean>;
|
|
157
171
|
abstract lockContainer(): Promise<void>;
|
|
158
172
|
abstract isContainerUnlocked(): boolean;
|
|
159
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Structural inspection of the container BEFORE unlock. `Workspace.open()`
|
|
175
|
+
* uses it to auto-select create-vs-open without a `create` flag:
|
|
176
|
+
* - `"empty"` — no container yet; safe to initialize a new workspace.
|
|
177
|
+
* - `"workspace"` — an existing wative workspace; open it.
|
|
178
|
+
* - `"foreign"` — non-empty storage that is NOT a wative workspace; `open()`
|
|
179
|
+
* refuses rather than scaffolding a workspace into it.
|
|
180
|
+
* The base default returns `"workspace"` (proceed as open-or-create, no guard),
|
|
181
|
+
* so existing custom providers are unaffected. Providers that can cheaply
|
|
182
|
+
* inspect their storage — e.g. `HybridProvider` on a filesystem — override this
|
|
183
|
+
* to detect `"foreign"` and stop a mistyped path from creating a workspace
|
|
184
|
+
* inside unrelated data.
|
|
185
|
+
*/
|
|
160
186
|
inspectContainer(): Promise<ContainerState>;
|
|
161
187
|
abstract loadRecord<T>(type: RecordType, slug?: Slug): Promise<Record$1<T>>;
|
|
162
188
|
abstract loadRecords<T>(type: RecordType): Promise<ReadonlyArray<Record$1<T>>>;
|
|
163
189
|
abstract writeRecord<T>(type: RecordType, slug: Slug, value: T): Promise<void>;
|
|
164
190
|
abstract dropRecord(type: RecordType, slug: Slug): Promise<void>;
|
|
165
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Initialize an empty container by writing the four root records
|
|
193
|
+
* (CONFIG, NETWORK, ASSET, LOG) from the bundled template files at
|
|
194
|
+
* `src/workspace_template/`. Idempotent: existing records are left
|
|
195
|
+
* untouched. The ACCOUNTS namespace is left empty — per-account records
|
|
196
|
+
* are created lazily by `accounts.create(...)`.
|
|
197
|
+
*
|
|
198
|
+
* Subclasses inherit this default; override only if a backend needs
|
|
199
|
+
* non-record initialization (e.g. allocating a database schema).
|
|
200
|
+
*/
|
|
166
201
|
initialize(): Promise<void>;
|
|
167
|
-
|
|
202
|
+
/** Any storage at all under the accounts area, however named. Defaults to
|
|
203
|
+
* "nothing", so a provider that does not implement it can only fail to add a
|
|
204
|
+
* refusal reason — never cause one. */
|
|
168
205
|
protected _hasRawAccountData(): Promise<boolean>;
|
|
169
206
|
abstract close(): Promise<void>;
|
|
170
207
|
}
|
|
171
208
|
|
|
172
|
-
type WativeErrorCode = "WORKSPACE_LOCKED" | "ACCOUNT_LOCKED" | "RECORD_LOCKED" | "BAD_PASSWORD" | "WEAK_PASSWORD" | "RECORD_NOT_FOUND" | "PROVIDER_IO" | "PERMISSION_DENIED" | "DISK_FULL" | "PARAMETER_ERROR" | "DECRYPT_FAILED" | "ENCRYPT_FAILED" | "ALGORITHM_IRREVERSIBLE" | "INVALID_MNEMONIC" | "INVALID_PRIVATE_KEY" | "UNSUPPORTED_NETWORK" | "TX_BUILD_FAILED" | "TX_SIGN_FAILED" | "TX_SUBMIT_FAILED" | "TX_TIMEOUT" | "TX_DROPPED" | "TX_ABORTED" | "RPC_UNREACHABLE"
|
|
209
|
+
type WativeErrorCode = "WORKSPACE_LOCKED" | "ACCOUNT_LOCKED" | "RECORD_LOCKED" | "BAD_PASSWORD" | "WEAK_PASSWORD" | "RECORD_NOT_FOUND" | "PROVIDER_IO" | "PERMISSION_DENIED" | "DISK_FULL" | "PARAMETER_ERROR" | "DECRYPT_FAILED" | "ENCRYPT_FAILED" | "ALGORITHM_IRREVERSIBLE" | "INVALID_MNEMONIC" | "INVALID_PRIVATE_KEY" | "UNSUPPORTED_NETWORK" | "TX_BUILD_FAILED" | "TX_SIGN_FAILED" | "TX_SUBMIT_FAILED" | "TX_TIMEOUT" | "TX_DROPPED" | "TX_ABORTED" | "RPC_UNREACHABLE"
|
|
210
|
+
|
|
211
|
+
| "RPC_REJECTED" | "STORAGE_NOT_DURABLE" | "UNSUPPORTED_OP";
|
|
173
212
|
interface WativeErrorOpts {
|
|
174
213
|
readonly cause?: unknown;
|
|
175
214
|
readonly details?: Record<string, unknown>;
|
|
@@ -312,8 +351,8 @@ declare class EvmTransaction implements Transaction {
|
|
|
312
351
|
}>;
|
|
313
352
|
address?: Address;
|
|
314
353
|
});
|
|
315
|
-
constructor(from: EvmAddress, to: string, value: bigint, data
|
|
316
|
-
chainId
|
|
354
|
+
constructor(from: EvmAddress, to: string, value: bigint, data: string | undefined, opts: {
|
|
355
|
+
chainId: ChainId;
|
|
317
356
|
type?: EvmTxType;
|
|
318
357
|
nonce?: number;
|
|
319
358
|
gasPrice?: bigint;
|
|
@@ -714,6 +753,8 @@ declare class Workspace {
|
|
|
714
753
|
|
|
715
754
|
_registerLiveAccount(account: Account): void;
|
|
716
755
|
|
|
756
|
+
_releaseAccount(account: Account): void;
|
|
757
|
+
|
|
717
758
|
_enqueueNetworkMutation<T>(fn: () => Promise<T>): Promise<T>;
|
|
718
759
|
private constructor();
|
|
719
760
|
get locked(): boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// 15 — Package resolution. Every
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// 15 — Package resolution. Every example now resolves the package by its bare
|
|
2
|
+
// name, so they all exercise the `exports` map; this file is the one that tests
|
|
3
|
+
// the map ITSELF rather than using it incidentally — every declared subpath,
|
|
4
|
+
// under both conditions. It therefore covers the export conditions (`import` vs
|
|
5
5
|
// `require`), the declared subpaths, and the parity between the two builds.
|
|
6
6
|
//
|
|
7
7
|
// It works without installing anything because Node resolves a package's own
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// 19 — The SHORT way to write a storage backend.
|
|
2
|
+
//
|
|
3
|
+
// Example 07 extends `Provider` and implements the record layer by hand, which
|
|
4
|
+
// is what you want when the storage system already has its own encryption or
|
|
5
|
+
// its own idea of a record. Most of the time you want this instead: extend
|
|
6
|
+
// `ContainerProvider` and implement six methods that move bytes around. It
|
|
7
|
+
// supplies the container session, the record API and all the encryption, so a
|
|
8
|
+
// working backend is about twenty lines.
|
|
9
|
+
//
|
|
10
|
+
// The store here is a plain Map with no persistence and no cleverness — the
|
|
11
|
+
// point is how little you have to write, not the store itself.
|
|
12
|
+
|
|
13
|
+
const test = require("node:test");
|
|
14
|
+
const assert = require("node:assert");
|
|
15
|
+
|
|
16
|
+
const { Workspace, ContainerProvider, WativeError } = require("wative-core");
|
|
17
|
+
|
|
18
|
+
/** A whole storage backend. Keys are strings, values are bytes. */
|
|
19
|
+
class MapProvider extends ContainerProvider {
|
|
20
|
+
#store = new Map();
|
|
21
|
+
#dirs = new Set([""]);
|
|
22
|
+
|
|
23
|
+
_exist(path) {
|
|
24
|
+
// A directory "exists" if anything sits under it — the library asks about
|
|
25
|
+
// both files and directories through this one method.
|
|
26
|
+
if (this.#store.has(path) || this.#dirs.has(path)) return true;
|
|
27
|
+
const prefix = path.endsWith("/") ? path : path + "/";
|
|
28
|
+
for (const k of this.#store.keys()) if (k.startsWith(prefix)) return true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_read(path) {
|
|
33
|
+
const v = this.#store.get(path);
|
|
34
|
+
if (!v) throw new WativeError("RECORD_NOT_FOUND", `nothing at ${path}`);
|
|
35
|
+
return v;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_write(path, data) {
|
|
39
|
+
this.#store.set(path, data);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_remove(path) {
|
|
43
|
+
this.#store.delete(path);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_ensureDir(path) {
|
|
47
|
+
this.#dirs.add(path);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_listItems(path) {
|
|
51
|
+
const prefix = path.endsWith("/") ? path : path + "/";
|
|
52
|
+
const names = new Set();
|
|
53
|
+
for (const k of this.#store.keys()) {
|
|
54
|
+
if (!k.startsWith(prefix)) continue;
|
|
55
|
+
const head = k.slice(prefix.length).split("/")[0];
|
|
56
|
+
if (head) names.add(head);
|
|
57
|
+
}
|
|
58
|
+
return [...names];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Not part of the contract — this example looks at the raw bytes below. */
|
|
62
|
+
dumpForTest() {
|
|
63
|
+
return this.#store;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
test("a twenty-line backend runs a whole workspace", async () => {
|
|
68
|
+
const provider = new MapProvider("map://desk");
|
|
69
|
+
|
|
70
|
+
let ws = await Workspace.open({ provider, password: "wsp-pwd" });
|
|
71
|
+
assert.strictEqual(ws.locked, false);
|
|
72
|
+
|
|
73
|
+
// The built-in networks are there, exactly as with any other backend.
|
|
74
|
+
assert.ok(ws.networks.map((n) => n.slug).includes("ethereum"));
|
|
75
|
+
|
|
76
|
+
const acc = await ws.accounts.create(
|
|
77
|
+
"Desk One",
|
|
78
|
+
"wsp-pwd",
|
|
79
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
|
80
|
+
);
|
|
81
|
+
await acc.deriveWallets(2);
|
|
82
|
+
assert.strictEqual(acc.wallets.length, 3);
|
|
83
|
+
|
|
84
|
+
// An address, and a signature from it — the full stack, on a Map.
|
|
85
|
+
const evm = acc.wallets[0].evm;
|
|
86
|
+
assert.match(evm.publicKey, /^0x[0-9a-fA-F]{40}$/);
|
|
87
|
+
const sig = await evm.signMessage("hello from a Map");
|
|
88
|
+
assert.match(sig, /^0x[0-9a-fA-F]{130}$/);
|
|
89
|
+
|
|
90
|
+
await ws.lock();
|
|
91
|
+
|
|
92
|
+
// Reopen from the same store: everything came back.
|
|
93
|
+
ws = await Workspace.open({ provider, password: "wsp-pwd" });
|
|
94
|
+
const again = ws.accounts.bySlug(acc.slug);
|
|
95
|
+
assert.ok(again);
|
|
96
|
+
assert.strictEqual(again.wallets.length, 3);
|
|
97
|
+
await again.tryUnlock("wsp-pwd");
|
|
98
|
+
assert.strictEqual(again.wallets[0].evm.publicKey, evm.publicKey);
|
|
99
|
+
|
|
100
|
+
await ws.lock();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("the encryption comes with the base class, not with your code", async () => {
|
|
104
|
+
// Nothing above encrypts anything, yet nothing readable reaches the store.
|
|
105
|
+
// That is the reason to extend `ContainerProvider`: a backend cannot weaken
|
|
106
|
+
// the encryption because a backend never performs it.
|
|
107
|
+
const provider = new MapProvider("map://opaque");
|
|
108
|
+
const ws = await Workspace.open({ provider, password: "wsp-pwd" });
|
|
109
|
+
await ws.accounts.create(
|
|
110
|
+
"Secret Holder",
|
|
111
|
+
"wsp-pwd",
|
|
112
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
|
113
|
+
);
|
|
114
|
+
await ws.lock();
|
|
115
|
+
|
|
116
|
+
const everything = Buffer.concat(
|
|
117
|
+
[...provider.dumpForTest().values()].map((b) => Buffer.from(b)),
|
|
118
|
+
).toString("latin1");
|
|
119
|
+
|
|
120
|
+
assert.ok(everything.length > 0, "the store should not be empty");
|
|
121
|
+
assert.ok(!everything.includes("abandon"), "the mnemonic must not appear in stored bytes");
|
|
122
|
+
assert.ok(!everything.includes("Secret Holder"), "the account name must not appear either");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("a wrong password cannot open the container", async () => {
|
|
126
|
+
const provider = new MapProvider("map://guarded");
|
|
127
|
+
const ws = await Workspace.open({ provider, password: "right-pwd" });
|
|
128
|
+
await ws.lock();
|
|
129
|
+
|
|
130
|
+
await assert.rejects(
|
|
131
|
+
Workspace.open({ provider, password: "wrong-pwd" }),
|
|
132
|
+
(err) => err.code === "BAD_PASSWORD",
|
|
133
|
+
);
|
|
134
|
+
});
|
package/examples/README.md
CHANGED
|
@@ -14,13 +14,13 @@ Most files are written as CommonJS (`.test.cjs`) because the CJS build is the mo
|
|
|
14
14
|
From the repo root:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
node --test
|
|
17
|
+
node --test examples/
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Or run a single file:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
node --test
|
|
23
|
+
node --test examples/01-quick-start.test.cjs
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
If you are reading this inside `node_modules` after installing the package, copy the folder out before running `--test` — Node's test runner skips anything under `node_modules`. A single file can still be run in place with plain `node <file>`.
|
|
@@ -37,7 +37,7 @@ All tests work fully offline — no RPC calls, no real networks. Each test creat
|
|
|
37
37
|
| [04-network-management.test.cjs](04-network-management.test.cjs) | The 10 pre-loaded networks, adding a brand-new network, overriding a pre-loaded RPC URL via `update()`, dropping user networks, the built-in protection guard. |
|
|
38
38
|
| [05-asset-management.test.cjs](05-asset-management.test.cjs) | The 25 pre-loaded tokens, adding a custom user token, id collision rejection, contract-address collision rejection, dropping user tokens, `workspace.filter(q, "Asset")` search. |
|
|
39
39
|
| [06-address-signing.test.cjs](06-address-signing.test.cjs) | Personal-message signing on EVM and Solana, building a transaction fully offline (no RPC). |
|
|
40
|
-
| [07-custom-provider.test.cjs](07-custom-provider.test.cjs) |
|
|
40
|
+
| [07-custom-provider.test.cjs](07-custom-provider.test.cjs) | Taking over the record layer as well, by extending `Provider` rather than `ContainerProvider` — for storage that has its own encryption or its own idea of a record. Longer than [19](19-simple-provider.test.cjs); start there unless you need this. |
|
|
41
41
|
| [08-persistence.test.cjs](08-persistence.test.cjs) | Round-trip across `lock()` + reopen — multiple accounts, user networks, custom assets, and logger configuration. |
|
|
42
42
|
| [09-workspace-filter.test.cjs](09-workspace-filter.test.cjs) | `workspace.filter(query, objective)` for all four objectives — Account (by name or slug), Wallet (by id or tag), Address (by public key), Asset (by symbol or contract address). |
|
|
43
43
|
| [10-default-network.test.cjs](10-default-network.test.cjs) | `account.setDefaultNetwork(value)` accepts a slug, a chain id, a hex chain id, or a `Network` instance. The choice persists across reopen. Unknown networks are rejected. |
|
|
@@ -48,22 +48,21 @@ All tests work fully offline — no RPC calls, no real networks. Each test creat
|
|
|
48
48
|
| [15-package-resolution.test.cjs](15-package-resolution.test.cjs) | Resolution by bare package name — every declared subpath under both `require()` and `import()`, identical export surfaces from the two builds, and the declared Node version. |
|
|
49
49
|
| [16-known-answer-vectors.test.cjs](16-known-answer-vectors.test.cjs) | Exact expected values — published BIP-44 addresses for the canonical mnemonic, a known address from an imported private key, and a fixed EIP-191 signature. Catches derivation from the wrong path or signing with the wrong key. |
|
|
50
50
|
| [17-locked-state.test.cjs](17-locked-state.test.cjs) | What locking enforces — which operations refuse and with which error code, that a locked account holds no mnemonic in the clear, and that unlocking restores identical behaviour. |
|
|
51
|
+
| [18-envelope-v3.test.cjs](18-envelope-v3.test.cjs) | `HybridProviderV3` — writing a workspace that derives one key for the whole workspace instead of one per stored secret, reopening it, and confirming a wrong password is still refused. |
|
|
52
|
+
| [19-simple-provider.test.cjs](19-simple-provider.test.cjs) | The short way to write a storage backend — extend `ContainerProvider` and implement six methods that move bytes. Runs a whole workspace on a `Map`, signs from a derived address, reopens it, and checks that nothing readable reaches the store. |
|
|
51
53
|
|
|
52
54
|
## Adapting for your project
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
```js
|
|
57
|
-
const { Workspace } = require("../dist/index.cjs");
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
with:
|
|
56
|
+
Nothing to change. Every file already imports the package by name:
|
|
61
57
|
|
|
62
58
|
```js
|
|
63
59
|
const { Workspace } = require("wative-core");
|
|
64
60
|
```
|
|
65
61
|
|
|
66
|
-
once you
|
|
62
|
+
so once you have run `pnpm add wative-core` (or `npm i wative-core`), copying a
|
|
63
|
+
file into your own project and running it works as-is. If your project is
|
|
64
|
+
ESM-flagged, use `import` instead — both resolve through the package's `exports`
|
|
65
|
+
map.
|
|
67
66
|
|
|
68
67
|
## Notes
|
|
69
68
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wative-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Hyperscale crypto wallet management library — multi-workspace, multi-chain, originally designed for on-chain market makers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.node.cjs",
|