@calimero-network/mero-js 26.10.0 → 27.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +73 -288
  2. package/dist/cloud-v2/attestation.d.ts +91 -0
  3. package/dist/cloud-v2/attestation.js +135 -0
  4. package/dist/cloud-v2/capabilities.d.ts +30 -0
  5. package/dist/cloud-v2/capabilities.js +31 -0
  6. package/dist/cloud-v2/client.d.ts +426 -0
  7. package/dist/cloud-v2/client.js +646 -0
  8. package/dist/cloud-v2/errors.d.ts +98 -0
  9. package/dist/cloud-v2/errors.js +165 -0
  10. package/dist/cloud-v2/index.d.ts +10 -0
  11. package/dist/cloud-v2/index.js +5 -0
  12. package/dist/cloud-v2/passkey.d.ts +27 -0
  13. package/dist/cloud-v2/passkey.js +73 -0
  14. package/dist/crypto/internal.d.ts +3 -37
  15. package/dist/crypto/internal.js +3 -72
  16. package/dist/index.browser.mjs +4 -2
  17. package/dist/index.browser.mjs.map +4 -4
  18. package/dist/index.cjs +1750 -12767
  19. package/dist/index.cjs.map +4 -4
  20. package/dist/index.d.ts +3 -56
  21. package/dist/index.js +14 -75
  22. package/dist/index.mjs +1750 -12767
  23. package/dist/index.mjs.map +4 -4
  24. package/dist/sealed/index.d.ts +4 -4
  25. package/dist/sealed/index.js +2 -2
  26. package/dist/sealed/release.d.ts +21 -76
  27. package/dist/sealed/release.js +24 -108
  28. package/dist/sealed/sealed.d.ts +8 -56
  29. package/dist/sealed/sealed.js +11 -89
  30. package/dist/sealed/verify.d.ts +7 -13
  31. package/dist/sealed/verify.js +3 -7
  32. package/package.json +8 -13
  33. package/dist/account/account-admin.d.ts +0 -80
  34. package/dist/account/account-admin.js +0 -346
  35. package/dist/account/account.d.ts +0 -196
  36. package/dist/account/account.js +0 -269
  37. package/dist/account/application-id.d.ts +0 -50
  38. package/dist/account/application-id.js +0 -83
  39. package/dist/account/bootstrap-from-invitation.d.ts +0 -110
  40. package/dist/account/bootstrap-from-invitation.js +0 -80
  41. package/dist/account/create-context.d.ts +0 -103
  42. package/dist/account/create-context.js +0 -267
  43. package/dist/account/govern.d.ts +0 -24
  44. package/dist/account/govern.js +0 -50
  45. package/dist/account/index.d.ts +0 -26
  46. package/dist/account/index.js +0 -15
  47. package/dist/account/join-as-account.d.ts +0 -23
  48. package/dist/account/join-as-account.js +0 -34
  49. package/dist/account/join-with-node.d.ts +0 -115
  50. package/dist/account/join-with-node.js +0 -161
  51. package/dist/account/node-admin.d.ts +0 -20
  52. package/dist/account/node-admin.js +0 -33
  53. package/dist/account/relay-from-invitation.d.ts +0 -137
  54. package/dist/account/relay-from-invitation.js +0 -180
  55. package/dist/account/relay-login-error.d.ts +0 -32
  56. package/dist/account/relay-login-error.js +0 -34
  57. package/dist/account/session.d.ts +0 -266
  58. package/dist/account/session.js +0 -915
  59. package/dist/account/wordlist.d.ts +0 -16
  60. package/dist/account/wordlist.js +0 -273
  61. package/dist/admin-api/admin-client.d.ts +0 -686
  62. package/dist/admin-api/admin-client.js +0 -1225
  63. package/dist/admin-api/admin-factory.d.ts +0 -14
  64. package/dist/admin-api/admin-factory.js +0 -22
  65. package/dist/admin-api/admin-types.d.ts +0 -2041
  66. package/dist/admin-api/admin-types.js +0 -12
  67. package/dist/admin-api/index.d.ts +0 -4
  68. package/dist/admin-api/index.js +0 -5
  69. package/dist/admin-api/redeem-invitation.d.ts +0 -101
  70. package/dist/admin-api/redeem-invitation.js +0 -200
  71. package/dist/auth/index.d.ts +0 -34
  72. package/dist/auth/index.js +0 -67
  73. package/dist/auth-api/auth-client.d.ts +0 -31
  74. package/dist/auth-api/auth-client.js +0 -117
  75. package/dist/auth-api/auth-factory.d.ts +0 -7
  76. package/dist/auth-api/auth-factory.js +0 -41
  77. package/dist/auth-api/auth-types.d.ts +0 -130
  78. package/dist/auth-api/auth-types.js +0 -2
  79. package/dist/auth-api/index.d.ts +0 -3
  80. package/dist/auth-api/index.js +0 -4
  81. package/dist/capabilities.d.ts +0 -84
  82. package/dist/capabilities.js +0 -117
  83. package/dist/cloud/account-ownership.d.ts +0 -63
  84. package/dist/cloud/account-ownership.js +0 -87
  85. package/dist/cloud/cloud-client.d.ts +0 -949
  86. package/dist/cloud/cloud-client.js +0 -1068
  87. package/dist/cloud/connect.d.ts +0 -213
  88. package/dist/cloud/connect.js +0 -260
  89. package/dist/cloud/enrol-redirect.d.ts +0 -181
  90. package/dist/cloud/enrol-redirect.js +0 -244
  91. package/dist/cloud/index.d.ts +0 -12
  92. package/dist/cloud/index.js +0 -10
  93. package/dist/cloud/link-redirect.d.ts +0 -95
  94. package/dist/cloud/link-redirect.js +0 -121
  95. package/dist/cloud/link-state.d.ts +0 -36
  96. package/dist/cloud/link-state.js +0 -104
  97. package/dist/cloud/routing-proof.d.ts +0 -113
  98. package/dist/cloud/routing-proof.js +0 -96
  99. package/dist/device-cert/device-cert.d.ts +0 -189
  100. package/dist/device-cert/device-cert.js +0 -239
  101. package/dist/device-cert/device-scope.d.ts +0 -58
  102. package/dist/device-cert/device-scope.js +0 -76
  103. package/dist/device-cert/index.d.ts +0 -4
  104. package/dist/device-cert/index.js +0 -3
  105. package/dist/ephemeral/codec.d.ts +0 -4
  106. package/dist/ephemeral/codec.js +0 -12
  107. package/dist/ephemeral/index.d.ts +0 -57
  108. package/dist/ephemeral/index.js +0 -101
  109. package/dist/ephemeral/types.d.ts +0 -27
  110. package/dist/ephemeral/types.js +0 -1
  111. package/dist/errors/classify.d.ts +0 -65
  112. package/dist/errors/classify.js +0 -117
  113. package/dist/errors/index.d.ts +0 -2
  114. package/dist/errors/index.js +0 -1
  115. package/dist/events/group.d.ts +0 -95
  116. package/dist/events/group.js +0 -10
  117. package/dist/events/index.d.ts +0 -5
  118. package/dist/events/index.js +0 -2
  119. package/dist/events/sse.d.ts +0 -127
  120. package/dist/events/sse.js +0 -469
  121. package/dist/events/ws.d.ts +0 -57
  122. package/dist/events/ws.js +0 -212
  123. package/dist/http-client/api-response.d.ts +0 -15
  124. package/dist/http-client/api-response.js +0 -1
  125. package/dist/http-client/http-factory.d.ts +0 -71
  126. package/dist/http-client/http-factory.js +0 -68
  127. package/dist/http-client/http-types.d.ts +0 -87
  128. package/dist/http-client/http-types.js +0 -1
  129. package/dist/http-client/index.d.ts +0 -7
  130. package/dist/http-client/index.js +0 -11
  131. package/dist/http-client/path-segment.d.ts +0 -1
  132. package/dist/http-client/path-segment.js +0 -8
  133. package/dist/http-client/retry.d.ts +0 -5
  134. package/dist/http-client/retry.js +0 -82
  135. package/dist/http-client/signal-utils.d.ts +0 -2
  136. package/dist/http-client/signal-utils.js +0 -41
  137. package/dist/http-client/web-client.d.ts +0 -80
  138. package/dist/http-client/web-client.js +0 -557
  139. package/dist/invitation/index.d.ts +0 -2
  140. package/dist/invitation/index.js +0 -2
  141. package/dist/invitation/invitation.d.ts +0 -106
  142. package/dist/invitation/invitation.js +0 -104
  143. package/dist/login/index.d.ts +0 -4
  144. package/dist/login/index.js +0 -3
  145. package/dist/login/login.d.ts +0 -111
  146. package/dist/login/login.js +0 -110
  147. package/dist/login/session.d.ts +0 -124
  148. package/dist/login/session.js +0 -141
  149. package/dist/mero-js.d.ts +0 -231
  150. package/dist/mero-js.js +0 -469
  151. package/dist/namespace-op/index.d.ts +0 -2
  152. package/dist/namespace-op/index.js +0 -3
  153. package/dist/namespace-op/namespace-op.d.ts +0 -150
  154. package/dist/namespace-op/namespace-op.js +0 -191
  155. package/dist/nodeDiscovery.d.ts +0 -47
  156. package/dist/nodeDiscovery.js +0 -101
  157. package/dist/owner-op/index.d.ts +0 -2
  158. package/dist/owner-op/index.js +0 -3
  159. package/dist/owner-op/owner-op.d.ts +0 -119
  160. package/dist/owner-op/owner-op.js +0 -214
  161. package/dist/presence/relay-presence.d.ts +0 -56
  162. package/dist/presence/relay-presence.js +0 -107
  163. package/dist/presence/statement.d.ts +0 -12
  164. package/dist/presence/statement.js +0 -17
  165. package/dist/relay/index.d.ts +0 -4
  166. package/dist/relay/index.js +0 -2
  167. package/dist/relay/nonce-source.d.ts +0 -72
  168. package/dist/relay/nonce-source.js +0 -111
  169. package/dist/relay/relay-client.d.ts +0 -472
  170. package/dist/relay/relay-client.js +0 -518
  171. package/dist/relay-attestation/attest-node-key.d.ts +0 -66
  172. package/dist/relay-attestation/attest-node-key.js +0 -110
  173. package/dist/relay-attestation/index.d.ts +0 -1
  174. package/dist/relay-attestation/index.js +0 -1
  175. package/dist/request/caller-proof.d.ts +0 -43
  176. package/dist/request/caller-proof.js +0 -36
  177. package/dist/request/index.d.ts +0 -3
  178. package/dist/request/index.js +0 -3
  179. package/dist/request/proof-signer.d.ts +0 -49
  180. package/dist/request/proof-signer.js +0 -59
  181. package/dist/request/request-sig.d.ts +0 -99
  182. package/dist/request/request-sig.js +0 -95
  183. package/dist/request-proof/authorizer.d.ts +0 -41
  184. package/dist/request-proof/authorizer.js +0 -60
  185. package/dist/request-proof/index.d.ts +0 -4
  186. package/dist/request-proof/index.js +0 -2
  187. package/dist/request-proof/request-proof.d.ts +0 -53
  188. package/dist/request-proof/request-proof.js +0 -95
  189. package/dist/rpc/index.d.ts +0 -64
  190. package/dist/rpc/index.js +0 -78
  191. package/dist/signer/index.d.ts +0 -2
  192. package/dist/signer/index.js +0 -3
  193. package/dist/signer/signer.d.ts +0 -92
  194. package/dist/signer/signer.js +0 -147
  195. package/dist/token-store/index.d.ts +0 -27
  196. package/dist/token-store/index.js +0 -62
  197. package/dist/transport/index.d.ts +0 -6
  198. package/dist/transport/index.js +0 -3
  199. package/dist/transport/mero-client.d.ts +0 -294
  200. package/dist/transport/mero-client.js +0 -386
  201. package/dist/transport/relay-observer.d.ts +0 -152
  202. package/dist/transport/relay-observer.js +0 -173
  203. package/dist/transport/relay-transport.d.ts +0 -73
  204. package/dist/transport/relay-transport.js +0 -94
  205. package/dist/transport/types.d.ts +0 -100
  206. package/dist/transport/types.js +0 -32
  207. package/dist/types/api-response.d.ts +0 -15
  208. package/dist/types/api-response.js +0 -1
  209. package/dist/types/index.d.ts +0 -1
  210. package/dist/types/index.js +0 -1
  211. package/dist/warrant/creation-warrant.d.ts +0 -163
  212. package/dist/warrant/creation-warrant.js +0 -194
  213. package/dist/warrant/governance-op.d.ts +0 -202
  214. package/dist/warrant/governance-op.js +0 -355
  215. package/dist/warrant/governance-warrant.d.ts +0 -124
  216. package/dist/warrant/governance-warrant.js +0 -164
  217. package/dist/warrant/index.d.ts +0 -8
  218. package/dist/warrant/index.js +0 -5
  219. package/dist/warrant/warrant.d.ts +0 -188
  220. package/dist/warrant/warrant.js +0 -202
@@ -1,58 +0,0 @@
1
- import { type Signer } from '../signer/signer.js';
2
- /** What a root scopes one device to. */
3
- export interface DeviceScopeInput {
4
- /**
5
- * The account root's signing secret, 64 hex. Pass this or {@link signer}.
6
- */
7
- rootSecret?: string;
8
- /**
9
- * The account root as a {@link Signer} — for a root that cannot be exported.
10
- * Its `publicKey` is the genesis key the proof carries, and the account is
11
- * derived from it.
12
- */
13
- signer?: Signer;
14
- /** The device being scoped, 64 hex — the one its certificate names. */
15
- device: string;
16
- /**
17
- * Application ids the device may speak for, 64 hex each. **Empty means all of
18
- * them**, which is also what the default is.
19
- *
20
- * A namespace binds the device only when its application is listed here (or
21
- * the list is empty), so a scope naming the wrong application yields a relay
22
- * refusal of `400`, not a binding.
23
- */
24
- applications?: readonly string[];
25
- /**
26
- * Orders scopes for this device: only a higher one supersedes. 0 for a
27
- * device's first scope.
28
- */
29
- scopeEpoch?: number;
30
- }
31
- /**
32
- * The 32 bytes a root signs to scope a device: `domain_hash` over account,
33
- * device, `scope_epoch` (u32 LE), `key_epoch` (u32 LE), then each application id
34
- * as its own length-prefixed part.
35
- */
36
- export declare function deviceScopePayload(input: {
37
- account: string;
38
- device: string;
39
- applications: readonly string[];
40
- scopeEpoch: number;
41
- keyEpoch: number;
42
- }): Promise<Uint8Array>;
43
- /**
44
- * Scope a device, returning the hex `AccountProof<DeviceScope>` a relay's
45
- * `linkAccountDevice` takes beside the credential.
46
- *
47
- * ```
48
- * AccountProof { genesis: AccountGenesis, chain: Vec<RootKeyHandoff>, statement: DeviceScope }
49
- * AccountGenesis { version: u8, root_sign_pk: [u8; 32] }
50
- * chain → u32-LE count (0 here)
51
- * DeviceScope { account, device: [u8; 32] x2, applications: Vec<[u8; 32]>,
52
- * scope_epoch: u32, key_epoch: u32, signature: [u8; 64] }
53
- * ```
54
- *
55
- * `chain` is empty and `key_epoch` 0 for the reason they are in
56
- * {@link signDeviceCert}: a root that was never handed off signs directly.
57
- */
58
- export declare function signDeviceScope(input: DeviceScopeInput): Promise<string>;
@@ -1,76 +0,0 @@
1
- /**
2
- * Scoping a device offline: which applications a device of an account may speak
3
- * for, signed by the account root.
4
- *
5
- * The certificate from {@link signDeviceCert} says *this key is a device of the
6
- * account*. It does not say where: that is this statement, and a namespace binds
7
- * a device only when both arrive together. A node that pairs a device signs the
8
- * scope for you; an account with **no node** has to sign it itself, next to the
9
- * certificate, and hand both to a relay's `linkAccountDevice`.
10
- *
11
- * Derived from core:
12
- *
13
- * - `DeviceScope` and `DeviceScope::signing_payload` — `crates/account/src/scope.rs`
14
- * - `DEVICE_SCOPE_SIGN_DOMAIN` — `crates/account/src/domain.rs`
15
- * - `AccountProof` / `AccountGenesis` borsh layout — `crates/account/src/{signed,account}.rs`
16
- *
17
- * The unit tests pin vectors produced by core's own `DeviceScope::sign`.
18
- */
19
- import { concat, domainHash, fromHex, hex, u32le } from '../crypto/internal.js';
20
- import { resolveSigner } from '../signer/signer.js';
21
- import { accountForRootPublicKey, ACCOUNT_GENESIS_VERSION } from './device-cert.js';
22
- const SCOPE_DOMAIN = new TextEncoder().encode('calimero.device.scope.v1');
23
- /**
24
- * The 32 bytes a root signs to scope a device: `domain_hash` over account,
25
- * device, `scope_epoch` (u32 LE), `key_epoch` (u32 LE), then each application id
26
- * as its own length-prefixed part.
27
- */
28
- export async function deviceScopePayload(input) {
29
- return domainHash(SCOPE_DOMAIN, [
30
- fromHex(input.account, 'account', 32),
31
- fromHex(input.device, 'device', 32),
32
- u32le(input.scopeEpoch),
33
- u32le(input.keyEpoch),
34
- ...input.applications.map((a, i) => fromHex(a, `applications[${i}]`, 32)),
35
- ]);
36
- }
37
- /**
38
- * Scope a device, returning the hex `AccountProof<DeviceScope>` a relay's
39
- * `linkAccountDevice` takes beside the credential.
40
- *
41
- * ```
42
- * AccountProof { genesis: AccountGenesis, chain: Vec<RootKeyHandoff>, statement: DeviceScope }
43
- * AccountGenesis { version: u8, root_sign_pk: [u8; 32] }
44
- * chain → u32-LE count (0 here)
45
- * DeviceScope { account, device: [u8; 32] x2, applications: Vec<[u8; 32]>,
46
- * scope_epoch: u32, key_epoch: u32, signature: [u8; 64] }
47
- * ```
48
- *
49
- * `chain` is empty and `key_epoch` 0 for the reason they are in
50
- * {@link signDeviceCert}: a root that was never handed off signs directly.
51
- */
52
- export async function signDeviceScope(input) {
53
- const signer = await resolveSigner(input.rootSecret, input.signer, 'rootSecret');
54
- const rootPublicKey = fromHex(signer.publicKey, 'signer.publicKey', 32);
55
- const account = await accountForRootPublicKey(hex(rootPublicKey));
56
- const applications = input.applications ?? [];
57
- const scopeEpoch = input.scopeEpoch ?? 0;
58
- // `u32le` would wrap a negative or oversized value into a different epoch
59
- // rather than refuse it, and the scope would then order itself wrongly.
60
- if (!Number.isInteger(scopeEpoch) || scopeEpoch < 0 || scopeEpoch > 4294967295) {
61
- throw new Error(`scopeEpoch must be a u32, got ${scopeEpoch}`);
62
- }
63
- const keyEpoch = 0;
64
- const payload = await deviceScopePayload({
65
- account,
66
- device: input.device,
67
- applications,
68
- scopeEpoch,
69
- keyEpoch,
70
- });
71
- const signature = await signer.sign(payload);
72
- if (signature.length !== 64) {
73
- throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
74
- }
75
- return hex(concat(new Uint8Array([ACCOUNT_GENESIS_VERSION]), rootPublicKey, u32le(0), fromHex(account, 'account', 32), fromHex(input.device, 'device', 32), u32le(applications.length), ...applications.map((a, i) => fromHex(a, `applications[${i}]`, 32)), u32le(scopeEpoch), u32le(keyEpoch), signature));
76
- }
@@ -1,4 +0,0 @@
1
- export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, accountForRootPublicKey, accountProofBytes, parseDeviceCredential, verifyDeviceCredential, } from './device-cert.js';
2
- export type { DeviceCertInput, DeviceCredential } from './device-cert.js';
3
- export { signDeviceScope, deviceScopePayload } from './device-scope.js';
4
- export type { DeviceScopeInput } from './device-scope.js';
@@ -1,3 +0,0 @@
1
- // Offline device certification — the account-root half of the keyholder story.
2
- export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, accountForRootPublicKey, accountProofBytes, parseDeviceCredential, verifyDeviceCredential, } from './device-cert.js';
3
- export { signDeviceScope, deviceScopePayload } from './device-scope.js';
@@ -1,4 +0,0 @@
1
- import type { Codec } from './types.js';
2
- /** Default codec: JSON text as a byte array. The wire carries `state` as
3
- * `number[]`, not a string, so this encodes to bytes rather than to text. */
4
- export declare function jsonCodec<T>(): Codec<T>;
@@ -1,12 +0,0 @@
1
- /** Default codec: JSON text as a byte array. The wire carries `state` as
2
- * `number[]`, not a string, so this encodes to bytes rather than to text. */
3
- export function jsonCodec() {
4
- return {
5
- encode(value) {
6
- return Array.from(new TextEncoder().encode(JSON.stringify(value)));
7
- },
8
- decode(bytes) {
9
- return JSON.parse(new TextDecoder().decode(new Uint8Array(bytes)));
10
- },
11
- };
12
- }
@@ -1,57 +0,0 @@
1
- import type { HttpClient } from '../http-client/index.js';
2
- import type { SseClient } from '../events/index.js';
3
- import { jsonCodec } from './codec.js';
4
- import type { Codec, EphemeralEntry } from './types.js';
5
- export { jsonCodec };
6
- export type { Codec, EphemeralEntry };
7
- /**
8
- * Ephemeral presence: transient, encrypted, never-persisted state (cursors,
9
- * typing, presence) that gossips between nodes without a WASM run or DAG
10
- * growth.
11
- *
12
- * The write/read asymmetry is the model, not an oversight. `set` takes no
13
- * author: you can only ever write your OWN slot, and the node resolves the
14
- * author server-side from its owned context identity — which is also why a
15
- * client cannot publish as somebody else. `subscribe` returns everyone's
16
- * slots, so each carries its `author`. The store is N independent
17
- * single-writer registers keyed by author; there is no merge across authors.
18
- *
19
- * There is a single read path: `subscribe`. On subscribing to a context, the
20
- * node replays that context's current presence to this connection as
21
- * ordinary `Ephemeral` events (carrying `ageMs`), before any live deltas
22
- * (which carry no `ageMs`). There is no separate snapshot RPC.
23
- */
24
- export declare class EphemeralClient {
25
- private httpClient;
26
- private sse;
27
- constructor(opts: {
28
- httpClient: HttpClient;
29
- sse: SseClient;
30
- });
31
- /**
32
- * Publish your own presence slice, replacing it wholesale.
33
- *
34
- * Rejects with an `RpcError` (not a bare `Error`), so a typed failure such as
35
- * an oversized slice keeps its `type` and `data` — e.g. `SliceTooLarge` with
36
- * the offending size — and is `instanceof RpcError` like every other RPC in
37
- * this SDK.
38
- */
39
- set<T>(contextId: string, state: T, codec?: Codec<T>): Promise<void>;
40
- /**
41
- * Observe presence changes for a context.
42
- *
43
- * This adds NO transport — it is a typed filter over the existing SSE event
44
- * stream, which already carries `{ contextId, type, data }`. On subscribing,
45
- * the node replays that context's current presence to this connection as
46
- * ordinary `Ephemeral` events before any live deltas; a replayed entry
47
- * carries `ageMs`, a live delta does not (never synthesized as `0` — absent
48
- * and zero mean different things). Returns an unsubscribe function.
49
- */
50
- subscribe<T>(contextId: string, handler: (entry: EphemeralEntry<T>) => void, codec?: Codec<T>): () => void;
51
- }
52
- /**
53
- * Presence for one context off an event stream: a typed filter over the SSE
54
- * events that already carry `{ contextId, type, data }`. Shared by a node
55
- * client and an account's relay client, which read presence the same way.
56
- */
57
- export declare function subscribePresence<T>(sse: SseClient, contextId: string, handler: (entry: EphemeralEntry<T>) => void, codec?: Codec<T>): () => void;
@@ -1,101 +0,0 @@
1
- import { jsonRpcCall } from '../rpc/index.js';
2
- import { jsonCodec } from './codec.js';
3
- export { jsonCodec };
4
- /**
5
- * Ephemeral presence: transient, encrypted, never-persisted state (cursors,
6
- * typing, presence) that gossips between nodes without a WASM run or DAG
7
- * growth.
8
- *
9
- * The write/read asymmetry is the model, not an oversight. `set` takes no
10
- * author: you can only ever write your OWN slot, and the node resolves the
11
- * author server-side from its owned context identity — which is also why a
12
- * client cannot publish as somebody else. `subscribe` returns everyone's
13
- * slots, so each carries its `author`. The store is N independent
14
- * single-writer registers keyed by author; there is no merge across authors.
15
- *
16
- * There is a single read path: `subscribe`. On subscribing to a context, the
17
- * node replays that context's current presence to this connection as
18
- * ordinary `Ephemeral` events (carrying `ageMs`), before any live deltas
19
- * (which carry no `ageMs`). There is no separate snapshot RPC.
20
- */
21
- export class EphemeralClient {
22
- constructor(opts) {
23
- this.httpClient = opts.httpClient;
24
- this.sse = opts.sse;
25
- }
26
- /**
27
- * Publish your own presence slice, replacing it wholesale.
28
- *
29
- * Rejects with an `RpcError` (not a bare `Error`), so a typed failure such as
30
- * an oversized slice keeps its `type` and `data` — e.g. `SliceTooLarge` with
31
- * the offending size — and is `instanceof RpcError` like every other RPC in
32
- * this SDK.
33
- */
34
- async set(contextId, state, codec = jsonCodec()) {
35
- await jsonRpcCall(this.httpClient, 'set_ephemeral', {
36
- contextId,
37
- state: codec.encode(state),
38
- });
39
- }
40
- /**
41
- * Observe presence changes for a context.
42
- *
43
- * This adds NO transport — it is a typed filter over the existing SSE event
44
- * stream, which already carries `{ contextId, type, data }`. On subscribing,
45
- * the node replays that context's current presence to this connection as
46
- * ordinary `Ephemeral` events before any live deltas; a replayed entry
47
- * carries `ageMs`, a live delta does not (never synthesized as `0` — absent
48
- * and zero mean different things). Returns an unsubscribe function.
49
- */
50
- subscribe(contextId, handler, codec = jsonCodec()) {
51
- return subscribePresence(this.sse, contextId, handler, codec);
52
- }
53
- }
54
- /**
55
- * Presence for one context off an event stream: a typed filter over the SSE
56
- * events that already carry `{ contextId, type, data }`. Shared by a node
57
- * client and an account's relay client, which read presence the same way.
58
- */
59
- export function subscribePresence(sse, contextId, handler, codec = jsonCodec()) {
60
- const listener = (event) => {
61
- const e = event;
62
- if (e.type !== 'Ephemeral' || e.contextId !== contextId)
63
- return;
64
- const data = e.data;
65
- if (!data?.author)
66
- return;
67
- // `removed` is omitted on an upsert, so normalize to a boolean here and
68
- // spare every caller the truthiness rule.
69
- const removed = Boolean(data.removed);
70
- const entry = {
71
- author: data.author,
72
- state: removed || data.state === undefined ? undefined : codec.decode(data.state),
73
- removed,
74
- };
75
- // `ageMs` is present only on a replayed seed entry; pass it through as-is
76
- // rather than defaulting a live delta to 0.
77
- if (data.ageMs !== undefined)
78
- entry.ageMs = data.ageMs;
79
- if (data.account !== undefined)
80
- entry.account = data.account;
81
- handler(entry);
82
- };
83
- sse.on('event', listener);
84
- // On a context this connection already holds, the node's one-time replay
85
- // went to whichever listener came first: seed this one from what the
86
- // client has kept, as the replay would have. (A context not held yet is
87
- // replayed by the node on the subscribe below.) Optional: a test double
88
- // or another event source may not keep presence.
89
- if (sse.isSubscribed?.(contextId)) {
90
- for (const data of sse.presenceSeed?.(contextId) ?? []) {
91
- listener({ contextId, type: 'Ephemeral', data });
92
- }
93
- }
94
- // Errors surface via the SSE client's own 'error' event; nothing more to
95
- // do with them here.
96
- void sse.connect().catch(() => undefined);
97
- void sse.subscribe([contextId]).catch(() => undefined);
98
- return () => {
99
- sse.off('event', listener);
100
- };
101
- }
@@ -1,27 +0,0 @@
1
- /** Encoding for a presence slice. The node never deserializes it — the wire
2
- * encoding is chosen client-side and travels client-to-client. */
3
- export interface Codec<T> {
4
- encode(value: T): number[];
5
- decode(bytes: number[]): T;
6
- }
7
- /** One entry from `subscribe`. `state` is absent on a removal; `removed` is
8
- * ABSENT (not false) on an upsert.
9
- *
10
- * On subscribing to a context, the node replays that context's current
11
- * presence to this connection as ordinary `Ephemeral` events (same shape as a
12
- * live delta), before any live deltas. `ageMs` distinguishes a replayed seed
13
- * entry from a live one: it is ABSENT on a live delta (fresh at receipt) and
14
- * PRESENT on a replayed entry, giving milliseconds since that author was last
15
- * heard from, measured on the node. Relative, so no clock agreement between
16
- * machines is needed. Bounded above by the node's 7s presence TTL. */
17
- export interface EphemeralEntry<T> {
18
- author: string;
19
- /**
20
- * The account a relay-carried update's device certificate names, hex. Absent
21
- * for a node's own presence.
22
- */
23
- account?: string;
24
- state?: T;
25
- removed?: boolean;
26
- ageMs?: number;
27
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,65 +0,0 @@
1
- /** The kind of failure, as far as it decides what the caller does next. */
2
- export type ErrorKind =
3
- /** 400. The request is wrong; sending it again cannot help. */
4
- 'invalid'
5
- /** 401. This client's session lapsed; signing in again can finish it. */
6
- | 'unauthorized'
7
- /** 403. The node will not do this for this caller. */
8
- | 'forbidden'
9
- /** 404. What the request names does not exist on this node (yet). */
10
- | 'not-found'
11
- /** 409. The request contradicts the current state: expired, spent, removed. */
12
- | 'conflict'
13
- /** 410. It existed and is gone for good. */
14
- | 'gone'
15
- /** 413. The body is over the node's limit. */
16
- | 'too-large'
17
- /** 429. The node asked the caller to slow down. */
18
- | 'rate-limited'
19
- /** 503 or 504. The node cannot serve this yet — no peer online, say. */
20
- | 'unavailable'
21
- /** Any other 5xx. The node failed; the request may well have been fine. */
22
- | 'server'
23
- /** Status 0. The request never reached the node: offline, CORS, a timeout. */
24
- | 'unreachable'
25
- /** Not an HTTP failure, or one with nothing more specific to go on. */
26
- | 'unknown';
27
- export interface ClassifiedError {
28
- kind: ErrorKind;
29
- /** The HTTP status, when the error carries one. 0 means no response. */
30
- status: number | undefined;
31
- /**
32
- * Whether the same request could succeed later without anything changing on
33
- * the caller's side. False for a refusal the node chose — asking again gets
34
- * the same answer — and true for a failure about the moment.
35
- */
36
- retryable: boolean;
37
- /**
38
- * The node's own explanation, without an `HTTP <status>` prefix, falling back
39
- * to the error's message. For logs and for a UI that shows detail; a UI that
40
- * wants friendly copy should switch on `kind`.
41
- */
42
- message: string;
43
- }
44
- /**
45
- * The HTTP status an error carries, in any shape a client throws: mero-js's
46
- * `HTTPError.status`, an axios-style `response.status`, or a bare `statusCode`.
47
- * Undefined when there is none — a thrown string, or an error raised before the
48
- * request was sent.
49
- */
50
- export declare function statusOf(err: unknown): number | undefined;
51
- /**
52
- * The most specific human-readable explanation an error carries, or
53
- * `fallback`. Prefers the node's words (`HTTPError.explanation`, or an axios
54
- * body's `error`/`message`) over a message that repeats the status line.
55
- */
56
- export declare function messageOf(err: unknown, fallback?: string): string;
57
- /**
58
- * What a failed request means: its kind, whether a later attempt could work,
59
- * and the node's own words for it.
60
- *
61
- * Accepts anything a `catch` hands over. A browser `fetch` that could not
62
- * connect throws a `TypeError`, which is read as `unreachable` like mero-js's
63
- * own status-0 `HTTPError`.
64
- */
65
- export declare function classifyError(err: unknown): ClassifiedError;
@@ -1,117 +0,0 @@
1
- import { RelayLoginError } from '../account/relay-login-error.js';
2
- const RETRYABLE = {
3
- invalid: false,
4
- // The session, not the request: signing in again and repeating it works.
5
- unauthorized: true,
6
- forbidden: false,
7
- // Too broad to give up on — the resource may simply not have synced yet.
8
- 'not-found': true,
9
- conflict: false,
10
- gone: false,
11
- 'too-large': false,
12
- 'rate-limited': true,
13
- unavailable: true,
14
- server: true,
15
- unreachable: true,
16
- unknown: true,
17
- };
18
- function kindForStatus(status) {
19
- switch (status) {
20
- case 0:
21
- return 'unreachable';
22
- case 400:
23
- return 'invalid';
24
- case 401:
25
- return 'unauthorized';
26
- case 403:
27
- return 'forbidden';
28
- case 404:
29
- return 'not-found';
30
- case 409:
31
- return 'conflict';
32
- case 410:
33
- return 'gone';
34
- case 413:
35
- return 'too-large';
36
- case 429:
37
- return 'rate-limited';
38
- case 503:
39
- case 504:
40
- return 'unavailable';
41
- }
42
- if (status >= 500 && status < 600)
43
- return 'server';
44
- if (status >= 400 && status < 500)
45
- return 'invalid';
46
- return 'unknown';
47
- }
48
- /**
49
- * The HTTP status an error carries, in any shape a client throws: mero-js's
50
- * `HTTPError.status`, an axios-style `response.status`, or a bare `statusCode`.
51
- * Undefined when there is none — a thrown string, or an error raised before the
52
- * request was sent.
53
- */
54
- export function statusOf(err) {
55
- if (!err || typeof err !== 'object')
56
- return undefined;
57
- const shaped = err;
58
- for (const candidate of [shaped.status, shaped.response?.status, shaped.statusCode]) {
59
- if (typeof candidate === 'number' && Number.isFinite(candidate))
60
- return candidate;
61
- }
62
- return undefined;
63
- }
64
- /**
65
- * The most specific human-readable explanation an error carries, or
66
- * `fallback`. Prefers the node's words (`HTTPError.explanation`, or an axios
67
- * body's `error`/`message`) over a message that repeats the status line.
68
- */
69
- export function messageOf(err, fallback = 'Request failed') {
70
- if (typeof err === 'string')
71
- return err.trim() || fallback;
72
- if (!err || typeof err !== 'object')
73
- return fallback;
74
- const shaped = err;
75
- for (const candidate of [
76
- shaped.explanation,
77
- shaped.response?.data?.error,
78
- shaped.response?.data?.message,
79
- shaped.data?.message,
80
- shaped.error,
81
- shaped.message,
82
- ]) {
83
- if (typeof candidate === 'string' && candidate.trim())
84
- return candidate;
85
- }
86
- return fallback;
87
- }
88
- /**
89
- * What a failed request means: its kind, whether a later attempt could work,
90
- * and the node's own words for it.
91
- *
92
- * Accepts anything a `catch` hands over. A browser `fetch` that could not
93
- * connect throws a `TypeError`, which is read as `unreachable` like mero-js's
94
- * own status-0 `HTTPError`.
95
- */
96
- export function classifyError(err) {
97
- // A relay login that failed before the request was sent: its own status is
98
- // about the login, so it is read by what it means for the request — a
99
- // refused credential is this client's session, anything else the relay's.
100
- if (err instanceof RelayLoginError) {
101
- const status = err.loginStatus;
102
- const kind = err.kind === 'signed-out' ? 'unauthorized' : status ? kindForStatus(status) : 'unreachable';
103
- return { kind, status, retryable: RETRYABLE[kind], message: messageOf(err) };
104
- }
105
- const status = statusOf(err);
106
- let kind;
107
- if (status !== undefined) {
108
- kind = kindForStatus(status);
109
- }
110
- else if (err instanceof TypeError) {
111
- kind = 'unreachable';
112
- }
113
- else {
114
- kind = 'unknown';
115
- }
116
- return { kind, status, retryable: RETRYABLE[kind], message: messageOf(err) };
117
- }
@@ -1,2 +0,0 @@
1
- export { classifyError } from './classify.js';
2
- export type { ClassifiedError, ErrorKind } from './classify.js';
@@ -1 +0,0 @@
1
- export { classifyError } from './classify.js';
@@ -1,95 +0,0 @@
1
- import type { GroupMemberRole } from '../admin-api/admin-types.js';
2
- /** Parsed group-membership event (core's untagged `NodeEvent` group variant). */
3
- export interface GroupMembershipEventData {
4
- groupId: string;
5
- type: 'MemberJoined' | 'MemberAdded' | 'MemberRemoved';
6
- data: {
7
- /** The member's ACCOUNT, hex-encoded 32 bytes (64 chars) - the principal the
8
- * governance rows name. Not a signing key: the field this replaced
9
- * carried one, and both are strings, so the rename is what makes a stale
10
- * consumer fail loudly instead of comparing accounts against keys forever. */
11
- memberAccount: string;
12
- /** Absent on `MemberRemoved`, and on a `MemberJoined` inherited from an Open
13
- * subgroup. Core's set is closed. */
14
- role?: GroupMemberRole;
15
- };
16
- }
17
- /**
18
- * A migration was accepted and its target ABI state version resolved.
19
- * `toStateVersion` is the `#[app::state(version = N)]` number, not the bundle
20
- * semver (`toVersion`).
21
- */
22
- export interface MigrationStartedData {
23
- fromVersion: string;
24
- toVersion: string;
25
- toStateVersion: number;
26
- /** Contexts the EMITTING NODE enumerated for the migration. Node-local, never
27
- * the fleet cohort size that `MigrationProgressData.total` carries. */
28
- localContextsTotal: number;
29
- }
30
- /**
31
- * Fleet rollup counters, recomputed as peers' heartbeat facts change. Counters
32
- * only: the per-member detail behind them stays in the admin-gated
33
- * `getMigrationStatus` read.
34
- */
35
- export interface MigrationProgressData {
36
- migrated: number;
37
- inProgress: number;
38
- unknown: number;
39
- failed: number;
40
- /** The pinned FLEET COHORT size, never a node-local context count like
41
- * `MigrationStartedData.localContextsTotal`. */
42
- total: number;
43
- }
44
- /**
45
- * One subgroup's local context swaps advanced on the emitting node.
46
- *
47
- * Admin-only: it names a descendant subgroup, which a plain namespace member
48
- * may not enumerate, so core withholds it from non-admin subscribers. A
49
- * non-admin never receiving this variant is correct behaviour, not a gap.
50
- */
51
- export interface CascadeProgressData {
52
- /** Hex-encoded descendant subgroup id, never the namespace root the event is
53
- * keyed on. */
54
- subgroupId: string;
55
- localContextsSwapped: number;
56
- /** Contexts the emitting node holds for THIS SUBGROUP. Node-local and
57
- * per-subgroup; unrelated to both sibling totals. */
58
- localContextsTotal: number;
59
- }
60
- /** Every pinned-cohort member reported the target version. */
61
- export interface MigrationCompletedData {
62
- toVersion: string;
63
- /** FLEET convergence, in seconds since the epoch: the same stamp
64
- * `MigrationStatus.fleetCompletedAt` returns. Not `GroupUpgradeStatus.completedAt`,
65
- * which is this node's own local swap completion. */
66
- completedAt: number;
67
- }
68
- /**
69
- * Parsed group-migration event (core's `GroupMigration` `NodeEvent` variant),
70
- * keyed on the namespace root a client subscribes with.
71
- *
72
- * `MigrationStarted`, `MigrationProgress` and `MigrationCompleted` reach every
73
- * subscribed member; `CascadeProgress` reaches namespace admins only.
74
- */
75
- export type GroupMigrationEventData = {
76
- groupId: string;
77
- type: 'MigrationStarted';
78
- data: MigrationStartedData;
79
- } | {
80
- groupId: string;
81
- type: 'MigrationProgress';
82
- data: MigrationProgressData;
83
- } | {
84
- groupId: string;
85
- type: 'CascadeProgress';
86
- data: CascadeProgressData;
87
- } | {
88
- groupId: string;
89
- type: 'MigrationCompleted';
90
- data: MigrationCompletedData;
91
- };
92
- /** Narrows a raw `'event'` payload to the group-migration family by its tag. */
93
- export declare function isGroupMigrationEvent(event: {
94
- type?: string;
95
- }): event is GroupMigrationEventData;
@@ -1,10 +0,0 @@
1
- const MIGRATION_EVENT_TYPES = new Set([
2
- 'MigrationStarted',
3
- 'MigrationProgress',
4
- 'CascadeProgress',
5
- 'MigrationCompleted',
6
- ]);
7
- /** Narrows a raw `'event'` payload to the group-migration family by its tag. */
8
- export function isGroupMigrationEvent(event) {
9
- return event.type !== undefined && MIGRATION_EVENT_TYPES.has(event.type);
10
- }
@@ -1,5 +0,0 @@
1
- export { SseClient } from './sse.js';
2
- export type { SseEventData, AppVersionChangedEvent } from './sse.js';
3
- export { WsClient } from './ws.js';
4
- export type { WsEventData } from './ws.js';
5
- export type { GroupMembershipEventData, GroupMigrationEventData, MigrationStartedData, MigrationProgressData, CascadeProgressData, MigrationCompletedData, } from './group.js';
@@ -1,2 +0,0 @@
1
- export { SseClient } from './sse.js';
2
- export { WsClient } from './ws.js';