@calimero-network/mero-js 29.15.0 → 29.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -125,6 +125,41 @@ The pieces it is built from are exported too: the sealed transport
125
125
  the client for mero-tee's private agent gate (`attestAgent`, `provisionAgent`,
126
126
  `releaseAgent`).
127
127
 
128
+ ## The runtime and the route layer
129
+
130
+ The client is two layers. The **runtime** (`@calimero-network/mero-js/runtime`,
131
+ `src/runtime/`) is everything that is not a route: the node's attestation, the
132
+ sealed channel, the credential and the session's refresh on a `401`, moving on
133
+ `421 WRONG_NODE`, D70 retries and failover, the `412 CONFIRM_REQUIRED` passkey
134
+ step-up, error mapping and server-sent events. Its whole surface toward routes is
135
+ one interface:
136
+
137
+ ```ts
138
+ interface Transport {
139
+ request(method: HttpMethod, path: string, options?: RequestOptions): Promise<Response>; // 2xx, body unread
140
+ json<T>(method: HttpMethod, path: string, options?: RequestOptions): Promise<T>;
141
+ events(path: string, options?: { query?; signal? }): AsyncGenerator<CloudEvent>;
142
+ }
143
+ // RequestOptions: { query?, body? (JSON) | bytes? + contentType?, passkey? (answers a 412),
144
+ // userVerification?, retry?: true | 'once' (D70), signal?, promptSignal? }
145
+ ```
146
+
147
+ A refusal is thrown as its typed `CalimeroError`. `NodeTransport` implements it;
148
+ `routePath('apps/{app}/blobs/{id}', { app, id })` fills a path with encoded
149
+ segments. The **route layer**, `CalimeroClient`, is the hand-written methods on
150
+ top; `client.transport` is its runtime, so a route layer of your own (one
151
+ generated from the node's OpenAPI document, say) shares the client's session:
152
+
153
+ ```ts
154
+ import { routePath } from '@calimero-network/mero-js/runtime';
155
+
156
+ const app = await client.transport.json('GET', routePath('apps/{app}', { app: appId }));
157
+ await client.transport.json('DELETE', routePath('orgs/{org}', { org }), { body: {}, passkey });
158
+ ```
159
+
160
+ The `/runtime` entry re-exports the package's own bundle, so its error classes are
161
+ the ones `CalimeroClient` throws (`instanceof` holds across both imports).
162
+
128
163
  ## Development
129
164
 
130
165
  ```bash
@@ -9,17 +9,13 @@
9
9
  * const { result } = await cloud.call(appId, 'set', { key: 'a', value: '1' });
10
10
  * ```
11
11
  */
12
+ import { DEFAULT_RETRY, NodeTransport, retryDelayMs, type CalimeroSession, type CloudEvent, type CloudRetryOptions } from '../runtime/transport.js';
12
13
  import { type AttestationDoc, type CloudPolicy } from './attestation.js';
13
14
  import { type PasskeyProvider } from './passkey.js';
14
15
  import { type AppsReport, type ConsentRequest, type ConsentResult, type ConsentStarted, type OpenConsentOptions, type OpsAppsReport, type OpsOrgsReport, type PageOptions, type PendingApprovals, type PublisherBindingInfo, type PublisherProof, type ReleaseRules } from './approvals.js';
15
16
  export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmPromptBlocked, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, RuntimeTooOld, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
16
17
  export type { Committed, SignUpRefusal } from './errors.js';
17
- export interface CalimeroSession {
18
- accountId: string;
19
- sessionToken: string;
20
- expiresAt: number;
21
- refreshToken: string;
22
- }
18
+ export type { CalimeroSession, CloudEvent };
23
19
  export interface CallResult {
24
20
  index: number;
25
21
  result: unknown;
@@ -32,11 +28,6 @@ export interface ViewResult {
32
28
  index: number;
33
29
  result: unknown;
34
30
  }
35
- export interface CloudEvent {
36
- id: string;
37
- kind: string;
38
- data: unknown;
39
- }
40
31
  /** What installing a package release answers. */
41
32
  export interface PackageInstalled {
42
33
  package: string;
@@ -703,31 +694,8 @@ export interface SignUpPolicy {
703
694
  /** Whether a sign-up without an invite is refused. */
704
695
  inviteRequired: boolean;
705
696
  }
706
- /**
707
- * How a call or a view is sent again while the node is unavailable (D70): with
708
- * the same key, after a backoff that starts at `baseDelayMs` and doubles up to
709
- * `maxDelayMs`, no jitter, at most `retries` times. The defaults are the Rust
710
- * client's: 6 retries, 200 ms doubling, at most 2 s (about 9 s in all, past a
711
- * leader election).
712
- */
713
- export interface CloudRetryOptions {
714
- /** Retries after the first attempt. Default 6. */
715
- retries?: number;
716
- /** The first backoff; each retry doubles it. Default 200 ms. */
717
- baseDelayMs?: number;
718
- /** The longest backoff. Default 2 s. */
719
- maxDelayMs?: number;
720
- }
721
- export declare const DEFAULT_RETRY: {
722
- readonly retries: 6;
723
- readonly baseDelayMs: 200;
724
- readonly maxDelayMs: 2000;
725
- };
726
- /** The backoff before retry `retry` (0 for the first): `baseDelayMs` doubling, at most `maxDelayMs`. */
727
- export declare function retryDelayMs(retry: number, options: {
728
- baseDelayMs: number;
729
- maxDelayMs: number;
730
- }): number;
697
+ export { DEFAULT_RETRY, retryDelayMs };
698
+ export type { CloudRetryOptions };
731
699
  export interface CalimeroClientOptions {
732
700
  /** One node of the cluster: the one the client talks to first. */
733
701
  baseUrl: string;
@@ -763,24 +731,15 @@ export interface CalimeroConnectOptions {
763
731
  random?: () => number;
764
732
  }
765
733
  export declare class CalimeroClient {
766
- /** Every node this client may use, `baseUrl` first (or as the directory last ordered them). */
767
- private nodeUrls;
768
- /** The one in use: an index into {@link nodeUrls}. */
769
- private at;
770
- /** A sealed fetch per node, each attesting its own node on first use. */
771
- private readonly channels;
772
- private readonly policy;
773
- /** The unsealed fetch, for the one route served in the clear (`/attestation`). */
774
- private readonly raw;
775
- private auth;
776
- private current;
777
- /** The refresh under way, which every request that needs one shares. */
778
- private refreshing;
779
- private readonly retry;
780
- /** Rejections of the underlying `fetch` itself: the node could not be reached or dropped the connection. */
781
- private readonly networkFailures;
782
- /** The directory the node list came from, for a client made by {@link connect}. */
783
- private source;
734
+ /**
735
+ * The runtime every route of this client is sent through
736
+ * (`@calimero-network/mero-js/runtime`): the attested, sealed channel to each
737
+ * node, the credential and its refresh, the `412 CONFIRM_REQUIRED` step-up,
738
+ * D70 retries and error mapping. A route layer of its own (one generated from
739
+ * the node's OpenAPI document, say) sends through it and shares this
740
+ * client's session.
741
+ */
742
+ readonly transport: NodeTransport;
784
743
  /**
785
744
  * A client for the Calimero Cloud cluster named `cluster`: its node list read
786
745
  * from the public node directory, those in `zone` first, each group in
@@ -792,87 +751,18 @@ export declare class CalimeroClient {
792
751
  * first request once the directory's `refreshAfterSeconds` has passed.
793
752
  */
794
753
  static connect(options: CalimeroConnectOptions): Promise<CalimeroClient>;
795
- constructor(options: CalimeroClientOptions);
754
+ /** A client of the nodes `options` names, or one sending through an existing `transport`. */
755
+ constructor(options: CalimeroClientOptions | NodeTransport);
796
756
  /** The node requests go to now: `baseUrl`, until a `421 WRONG_NODE` or a failover moved the client on. */
797
757
  get nodeUrl(): string;
798
758
  /** Every node this client may use, in the order it moves through them. */
799
759
  get nodes(): string[];
800
- /** The sealed channel to `url`, attested under the client's policy before its first request. */
801
- private channel;
802
- /**
803
- * Move on from node `from` (the one that answered `421 WRONG_NODE`) to the
804
- * next one. False when there is no other. A request that saw the same node
805
- * refuse after another request moved on already does not move again.
806
- */
807
- private moveOn;
808
- /**
809
- * Fail over from node `from`, where a request's retries ran out, to the next
810
- * node it has not `tried`. False when it has tried them all. A request whose
811
- * node another request moved away from already goes to that node, if untried.
812
- */
813
- private failOver;
814
- /**
815
- * Read the node list again from the directory, in the background, for a
816
- * client made by {@link connect}: the node in use stays first, then the
817
- * directory's order; a node no longer listed is dropped. A failed read is
818
- * ignored. One read at a time.
819
- */
820
- private reread;
821
760
  /** Authenticate every request with an API key. */
822
761
  useApiKey(key: string): void;
823
762
  useSession(session: CalimeroSession): void;
824
763
  get session(): CalimeroSession | undefined;
825
- /**
826
- * Send one request to the node in use, with the credential held now, and
827
- * answer the response, whatever its status. {@link send} judges it.
828
- */
829
- private request;
830
- /**
831
- * {@link request}, answering what a lost issuing node or a node on the
832
- * wrong release does to a session: a successful response, or the refusal as a
833
- * typed error ({@link errorFrom}).
834
- *
835
- * - `401 UNAUTHENTICATED` to a request that carried a session: the node that
836
- * signed the session may be gone, replaced, or out of reach of the node
837
- * asked, which any node of the cluster answers with a refresh. The client
838
- * refreshes once, shared by every request that needs it at the same time
839
- * ({@link refresh}), and repeats the request once. A refresh that is
840
- * refused surfaces as {@link LoginRequired}.
841
- * - `421 WRONG_NODE`: this node's release may not serve the account. The
842
- * client moves to the next node it knows and repeats the request once; with
843
- * none, the {@link WrongNode} reaches the caller.
844
- *
845
- * Repeating is safe even for a request that is not idempotent: the node
846
- * answers `401` and `421` when it judges the credential, before it reads what
847
- * the request asks, so the first attempt ran nothing. The exceptions are the
848
- * ceremonies that mint a session ({@link ceremony}), never repeated here.
849
- */
850
- private send;
851
- /** Whether a `401` to a request sent with `token` is one a refresh may answer. */
852
- private mayRefresh;
853
- /**
854
- * A fresh session in place of the one `token` belonged to: by the refresh
855
- * another request already made, or by one this request starts.
856
- */
857
- private renew;
764
+ /** A JSON request, or a raw one when `raw` is given (as `contentType`, the transport's default when absent). */
858
765
  private json;
859
- /**
860
- * {@link json} for a call or a view (D70): while the node answers 503 or
861
- * cannot be reached, the same request, with the same idempotency key, is sent
862
- * again after {@link retryDelayMs} (or the pause the node asked for, if
863
- * longer), at most `retries` times. Every other answer is final.
864
- *
865
- * When a node's retries run out, the request fails over to the next node the
866
- * client knows and its retries start again there, with the same key: the
867
- * key is kept in the app's replicated log, so the cluster answers it once
868
- * whichever node it reaches. Each node is tried once per request. The last
869
- * failure surfaces as {@link UnavailableError}: the node's 503, or reason
870
- * `network` for a connection that failed, whose `committed` is `'unknown'`.
871
- *
872
- * `once` sends the request exactly once, to the node in use (the confirmed
873
- * attempt of {@link callConfirmed}, whose challenge is single-use).
874
- */
875
- private retried;
876
766
  post<T = Record<string, unknown>>(path: string, body?: unknown): Promise<T>;
877
767
  get<T = Record<string, unknown>>(path: string): Promise<T>;
878
768
  put<T = Record<string, unknown>>(path: string, body?: unknown): Promise<T>;
@@ -917,7 +807,6 @@ export declare class CalimeroClient {
917
807
  * same token again (which would revoke the session).
918
808
  */
919
809
  refresh(): Promise<CalimeroSession>;
920
- private exchangeRefresh;
921
810
  /**
922
811
  * End this session. The client is signed out whether or not the node
923
812
  * answers: the revoke (`POST /auth/logout`) is attempted, and its failure
@@ -1177,6 +1066,10 @@ export declare class CalimeroClient {
1177
1066
  * confirmed retry carries `confirm` beside it.
1178
1067
  */
1179
1068
  deleteConfirmed<T>(path: string, passkey: PasskeyProvider, body?: Record<string, unknown>): Promise<T>;
1069
+ /**
1070
+ * `body` to `path`, answering a `412 CONFIRM_REQUIRED` with `passkey` when one
1071
+ * is given; `signal` aborts only the passkey prompt.
1072
+ */
1180
1073
  private confirmed;
1181
1074
  /**
1182
1075
  * Create an app instance from a bundle hash, or from a package release
@@ -1219,8 +1112,6 @@ export declare class CalimeroClient {
1219
1112
  }): Promise<ViewResult>;
1220
1113
  /** Committed events from index `from`, then live, until `signal` aborts. */
1221
1114
  events(app: string, from?: number, signal?: AbortSignal): AsyncGenerator<CloudEvent>;
1222
- /** A server-sent event stream on `path`, sealed like every other answer. */
1223
- private stream;
1224
1115
  /**
1225
1116
  * Publish this device's presence in `app`: `state` (at most 4 KiB) or `null`
1226
1117
  * to retract it. The newest `seq` wins; send the same `seq` and state again