@calimero-network/mero-js 28.0.0 → 28.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.
@@ -26,6 +26,10 @@ export interface Governance {
26
26
  releases: Authority;
27
27
  appUpgrades: Authority;
28
28
  outbound: Authority;
29
+ /** Who may install apps: any account (`open`, left out) or granted deployers only. */
30
+ deployments?: 'open' | 'granted';
31
+ /** Who grants deployers: the operator (left out) or the cluster's owner. */
32
+ deployers?: 'operator' | 'owner';
29
33
  }
30
34
  /**
31
35
  * Who upgrades a package's instances, as its signed manifest declares
@@ -72,6 +72,8 @@ export interface AppInfo {
72
72
  bundle: string;
73
73
  package: string | null;
74
74
  version: string | null;
75
+ /** The deployment it was created from, if any. */
76
+ deployment?: string | null;
75
77
  }
76
78
  /**
77
79
  * An organization-wide upgrade run, as `upgradeOrg`, `upgradeRun` and
@@ -130,6 +132,145 @@ export interface OrgInfo extends Record<string, unknown> {
130
132
  after: string;
131
133
  } | null;
132
134
  }
135
+ /** A blob's bytes with the content type the node serves them as. */
136
+ export interface BlobContent {
137
+ data: Uint8Array;
138
+ /** What the uploader gave (component apps); `application/octet-stream` otherwise. */
139
+ contentType: string;
140
+ }
141
+ /** One xcall that was never delivered (core's `DeadLetter`). */
142
+ export interface DeadLetter {
143
+ seq: number;
144
+ /** The target app. */
145
+ app: string;
146
+ method: string;
147
+ reason: string;
148
+ /** The log entry's timestamp it was dead-lettered at (ms since the epoch). */
149
+ atMs: number;
150
+ }
151
+ /** `GET /apps/{app}/xcalls`: a component app's outbox and its dead letters, newest last. */
152
+ export interface AppXcalls {
153
+ /** Messages waiting in the outbox. */
154
+ pending: number;
155
+ deadLetters: DeadLetter[];
156
+ }
157
+ /**
158
+ * An account or an organization, as a deployer, a deployment's owner or the
159
+ * cluster's owner: `{ account: id }` or `{ org: id }`.
160
+ */
161
+ export type Deployer = {
162
+ account: string;
163
+ } | {
164
+ org: string;
165
+ };
166
+ /** A deployer's key, as `GET /deployers` lists it and `DELETE /deployers/{key}` takes it. */
167
+ export declare function deployerKey(deployer: Deployer): string;
168
+ /** One deployer grant, as `GET /deployers` lists it. */
169
+ export interface DeployerGrant {
170
+ /** `account:{id}` or `org:{id}`. */
171
+ key: string;
172
+ deployer: Deployer | null;
173
+ /** RFC 3339; `null`: it does not end. */
174
+ until: string | null;
175
+ untilSecs: number | null;
176
+ grantedAt: string;
177
+ grantedBy: string;
178
+ }
179
+ /** `GET /deployers`: who may install apps on the cluster, and who grants it. */
180
+ export interface DeployersInfo {
181
+ /** `open` (any account) or `granted` (deployers only); absent on a default cluster. */
182
+ deployments?: 'open' | 'granted';
183
+ /** Who grants deployers: `operator` (its key) or `owner` (the cluster's owner, with a passkey). */
184
+ deployers?: 'operator' | 'owner';
185
+ owner: Deployer | null;
186
+ grants: DeployerGrant[];
187
+ }
188
+ /** One app a deployment offers, as its managers read it. */
189
+ export interface DeploymentOffer {
190
+ /** The app's name in the deployment. */
191
+ app: string;
192
+ package: string;
193
+ version: string;
194
+ addedBy: string;
195
+ /** RFC 3339. */
196
+ addedAt: string;
197
+ }
198
+ /** A deployment as its managers read it (`GET /deployments`). */
199
+ export interface DeploymentInfo {
200
+ id: string;
201
+ name: string;
202
+ owner: Deployer;
203
+ apps: DeploymentOffer[];
204
+ createdBy: string;
205
+ /** RFC 3339. */
206
+ createdAt: string;
207
+ }
208
+ /**
209
+ * `GET /deployments/{id}`, public: what a frontend and the customer's consent
210
+ * show. Never its managers, members or customers.
211
+ */
212
+ export interface PublicDeployment {
213
+ id: string;
214
+ name: string;
215
+ /** The owner's kind and name (`null` if it is gone). */
216
+ owner: {
217
+ kind: 'account' | 'org';
218
+ name: string | null;
219
+ };
220
+ apps: Array<{
221
+ app: string;
222
+ package: string;
223
+ version: string;
224
+ }>;
225
+ }
226
+ /** `GET /deployments/{id}/orgs`: the organizations with instances created from a deployment. */
227
+ export interface DeploymentOrgs {
228
+ deployment: string;
229
+ orgs: Array<{
230
+ orgId: string;
231
+ name: string | null;
232
+ instances: number;
233
+ /** Which of the deployment's apps they run, at which release. */
234
+ apps: Array<{
235
+ app: string;
236
+ version: string;
237
+ }>;
238
+ /** RFC 3339: when its first instance from the deployment was created. */
239
+ firstCreatedAt: string;
240
+ }>;
241
+ /** Instances outside any organization, counted only. */
242
+ outsideOrgs: number;
243
+ }
244
+ /**
245
+ * A package frontend's `/.well-known/calimero.json`: the package, its
246
+ * releases, the deployments offering it and the cluster's API URLs.
247
+ */
248
+ export interface FrontendManifest {
249
+ package: string;
250
+ versions: string[];
251
+ deployments: Array<{
252
+ id: string;
253
+ name: string;
254
+ app: string;
255
+ version: string;
256
+ }>;
257
+ api: string[];
258
+ }
259
+ /**
260
+ * Read the `/.well-known/calimero.json` of the frontend at `origin` (by
261
+ * default the page's own). Served in the clear by the frontend host, like the
262
+ * page itself: for choosing a deployment and the API URL to connect to, never
263
+ * for trust (the client attests the node it then talks to).
264
+ */
265
+ export declare function frontendManifest(origin?: string, fetcher?: typeof fetch): Promise<FrontendManifest>;
266
+ /**
267
+ * The deployment a frontend page was opened for: the node serves
268
+ * `/d/{deployment}/` by redirecting to `/{version}/…?deployment={id}`, so the
269
+ * page creates its instances from that deployment
270
+ * (`createApp(name, { deployment, package })`). `null` when the page was
271
+ * not opened through a deployment. `href` defaults to `window.location.href`.
272
+ */
273
+ export declare function pageDeployment(href?: string): string | null;
133
274
  /**
134
275
  * How a call or a view is sent again while the node is unavailable (D70): with
135
276
  * the same key, after a backoff that starts at `baseDelayMs` and doubles up to
@@ -359,14 +500,36 @@ export declare class CalimeroClient {
359
500
  }>;
360
501
  /** Upload a raw WASM bundle (development clusters only); answers its hash. */
361
502
  uploadBundle(wasm: Uint8Array): Promise<string>;
362
- /** Upload a blob for `app`: `blobId` is base58 (what apps print), `id` hex. */
363
- uploadBlob(app: string, data: Uint8Array): Promise<{
503
+ /**
504
+ * Upload a blob for `app`: `blobId` is base58 (what apps print), `id` hex.
505
+ * `contentType` (default `application/octet-stream`; printable ASCII, the
506
+ * node refuses another with `400`) is sent as the upload's `Content-Type`:
507
+ * a component app records it and serves downloads with it. A component
508
+ * app holding as many blobs as it may answers `429 QUOTA_EXCEEDED`.
509
+ */
510
+ uploadBlob(app: string, data: Uint8Array, contentType?: string | {
511
+ contentType?: string;
512
+ }): Promise<{
364
513
  blobId: string;
365
514
  id: string;
366
515
  size: number;
367
516
  }>;
368
517
  /** One of `app`'s blobs, by id (base58 or hex). */
369
518
  downloadBlob(app: string, id: string): Promise<Uint8Array>;
519
+ /**
520
+ * Download a component app's blob with a grant one of its runs handed out
521
+ * (`BlobRef::grant`, up to 24 h). No session needed: the grant is the
522
+ * permission. A bad or expired grant answers `NotFound`, as a missing blob.
523
+ */
524
+ downloadBlobGranted(app: string, id: string, grant: string): Promise<Uint8Array>;
525
+ /**
526
+ * {@link downloadBlob} (or, with `grant`, {@link downloadBlobGranted}) with
527
+ * the content type the node serves it as: the uploader's, for a component
528
+ * app; `application/octet-stream` otherwise.
529
+ */
530
+ blobContent(app: string, id: string, options?: {
531
+ grant?: string;
532
+ }): Promise<BlobContent>;
370
533
  /** `app`'s blobs. */
371
534
  listBlobs(app: string): Promise<BlobInfo[]>;
372
535
  /** One of `app`'s blobs' id and size; `NotFound` if the app has no such blob. */
@@ -395,6 +558,13 @@ export declare class CalimeroClient {
395
558
  setAccess(app: string, access: Record<string, unknown>): Promise<void>;
396
559
  /** Set the app instance's metadata (its owner, or an admin of its organization); at most 16 KiB. */
397
560
  setAppMetadata(app: string, metadata: unknown): Promise<void>;
561
+ /**
562
+ * A component app's xcalls: how many messages wait in its outbox, and the
563
+ * dead letters (never delivered), newest last. Its owner's or an org
564
+ * admin's (`403` to anyone else); `409 NOT_SUPPORTED` for an app on the
565
+ * wasmer runtime, whose xcalls are follow-ups.
566
+ */
567
+ appXcalls(app: string): Promise<AppXcalls>;
398
568
  /** What the app's storage holds: `{ keys, bytes, blobs, blobBytes }`. */
399
569
  appStorage(app: string): Promise<Record<string, unknown>>;
400
570
  /** The ABI the app's bundle embeds: `{ bundle, abi }`. */
@@ -443,12 +613,19 @@ export declare class CalimeroClient {
443
613
  /**
444
614
  * Create an app instance from a bundle hash, or from a package release
445
615
  * (`{ package, version?, service? }`, installed from the registry first if
446
- * the cluster lacks it).
616
+ * the cluster lacks it). With `deployment`, the instance runs the release
617
+ * that deployment offers for `package` (`version`, if given, must be it)
618
+ * and records the deployment; no install, no deployer grant. A package or
619
+ * version it does not offer answers `400 NOT_OFFERED`, several offered
620
+ * releases with no `version` `400 VERSION_REQUIRED`, and an unknown
621
+ * `service` `400 NO_SUCH_SERVICE`. A page opened through `/d/{deployment}/`
622
+ * finds its deployment with {@link pageDeployment}.
447
623
  */
448
624
  createApp(name: string, bundle: string | {
449
625
  package: string;
450
626
  version?: string;
451
627
  service?: string;
628
+ deployment?: string;
452
629
  }, options?: {
453
630
  org?: string;
454
631
  access?: Record<string, unknown>;
@@ -679,4 +856,69 @@ export declare class CalimeroClient {
679
856
  uses?: number;
680
857
  }): Promise<string>;
681
858
  redeemInvite(token: string): Promise<Record<string, unknown>>;
859
+ /**
860
+ * Let an account or organization install apps where the cluster's
861
+ * deployments are `granted`, until `until` (RFC 3339 or a `Date`; absent,
862
+ * the grant does not end). The cluster's owner grants it, approved with
863
+ * `passkey`, where governance says `deployers: owner`; elsewhere the
864
+ * operator grants with its key, and this is refused `403`.
865
+ */
866
+ grantDeployer(deployer: Deployer, passkey: PasskeyProvider, options?: {
867
+ until?: Date | string;
868
+ }): Promise<{
869
+ key: string;
870
+ deployer: Deployer;
871
+ grant: Record<string, unknown>;
872
+ }>;
873
+ /**
874
+ * Who may install apps on the cluster: its deployments rule, who grants
875
+ * deployers, its owner and every grant. For the cluster's owner, or the
876
+ * operator (its token, `useApiKey`).
877
+ */
878
+ deployers(): Promise<DeployersInfo>;
879
+ /**
880
+ * End a deployer's grant (`deployer`, or its key `account:{id}` /
881
+ * `org:{id}`), as the cluster's owner, approved with `passkey`. Apps it
882
+ * installed, and the apps running them, stay.
883
+ */
884
+ revokeDeployer(deployer: Deployer | string, passkey: PasskeyProvider): Promise<void>;
885
+ /**
886
+ * Create a deployment: `owner`'s (the caller's own account, or an
887
+ * organization where it holds `MANAGE_APPLICATION` org-wide) list of the
888
+ * apps it offers, named `name` (1 to 64 characters, one line) for
889
+ * customers. Approved with `passkey`; where deployments are `granted` the
890
+ * caller needs a deployer grant. Answers the deployment's id.
891
+ */
892
+ createDeployment(name: string, owner: Deployer, passkey: PasskeyProvider): Promise<string>;
893
+ /** The deployments the caller manages: its account's, and its organizations' where it holds `MANAGE_APPLICATION`. */
894
+ deployments(): Promise<DeploymentInfo[]>;
895
+ /** A deployment as anyone may read it: its name, its owner's kind and name, and the apps it offers. Public. */
896
+ deployment(id: string): Promise<PublicDeployment>;
897
+ /** Delete a deployment that offers nothing and has no instances, approved with `passkey`. */
898
+ deleteDeployment(id: string, passkey: PasskeyProvider): Promise<void>;
899
+ /**
900
+ * Offer `package@version` (installed, and seen by the caller) as `app`
901
+ * (1 to 64 letters, digits, `.`, `_`, `-`) in deployment `id`, or move an
902
+ * offered app to another release of its package. Approved with `passkey`.
903
+ */
904
+ offerApp(id: string, offer: {
905
+ app: string;
906
+ package: string;
907
+ version: string;
908
+ }, passkey: PasskeyProvider): Promise<{
909
+ deployment: string;
910
+ app: string;
911
+ offer: Record<string, unknown>;
912
+ }>;
913
+ /**
914
+ * Withdraw `app` from deployment `id`, approved with `passkey`; refused
915
+ * while an instance created from the deployment still runs it.
916
+ */
917
+ withdrawApp(id: string, app: string, passkey: PasskeyProvider): Promise<void>;
918
+ /**
919
+ * The organizations with instances created from deployment `id`, for its
920
+ * managers: each one's id, name, instance count, apps and releases, and
921
+ * first instance. Never members, teams or app data.
922
+ */
923
+ deploymentOrgs(id: string): Promise<DeploymentOrgs>;
682
924
  }
@@ -16,6 +16,44 @@ import { CalimeroError, ConfirmCancelled, ConfirmRequired, LoginRequired, Unavai
16
16
  import { b64url, fromB64url } from './passkey.js';
17
17
  import { confirmationCode, pageQuery, pendingApprovalsFrom, } from './approvals.js';
18
18
  export { AppError, ArgsTooLarge, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, ReleaseDeadlineError, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
19
+ /** A deployer's key, as `GET /deployers` lists it and `DELETE /deployers/{key}` takes it. */
20
+ export function deployerKey(deployer) {
21
+ return 'account' in deployer ? `account:${deployer.account}` : `org:${deployer.org}`;
22
+ }
23
+ /**
24
+ * Read the `/.well-known/calimero.json` of the frontend at `origin` (by
25
+ * default the page's own). Served in the clear by the frontend host, like the
26
+ * page itself: for choosing a deployment and the API URL to connect to, never
27
+ * for trust (the client attests the node it then talks to).
28
+ */
29
+ export async function frontendManifest(origin, fetcher = fetch) {
30
+ const base = origin ?? globalThis.location?.origin;
31
+ if (!base)
32
+ throw new Error('no page origin to read the frontend manifest from; pass `origin`');
33
+ const response = await fetcher(new URL('/.well-known/calimero.json', base).toString());
34
+ if (!response.ok)
35
+ throw await refusal(response);
36
+ const manifest = (await response.json());
37
+ return { ...manifest, deployments: manifest.deployments ?? [] };
38
+ }
39
+ /**
40
+ * The deployment a frontend page was opened for: the node serves
41
+ * `/d/{deployment}/` by redirecting to `/{version}/…?deployment={id}`, so the
42
+ * page creates its instances from that deployment
43
+ * (`createApp(name, { deployment, package })`). `null` when the page was
44
+ * not opened through a deployment. `href` defaults to `window.location.href`.
45
+ */
46
+ export function pageDeployment(href) {
47
+ const at = href ?? globalThis.location?.href;
48
+ if (!at)
49
+ return null;
50
+ try {
51
+ return new URL(at).searchParams.get('deployment') || null;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
19
57
  /** One URL path segment, so an id cannot reach another route. */
20
58
  function seg(value) {
21
59
  return encodeURIComponent(value);
@@ -535,14 +573,41 @@ export class CalimeroClient {
535
573
  const { hash } = await this.json('POST', 'bundles', undefined, wasm);
536
574
  return hash;
537
575
  }
538
- /** Upload a blob for `app`: `blobId` is base58 (what apps print), `id` hex. */
539
- uploadBlob(app, data) {
540
- return this.json('POST', `apps/${seg(app)}/blobs`, undefined, data);
576
+ /**
577
+ * Upload a blob for `app`: `blobId` is base58 (what apps print), `id` hex.
578
+ * `contentType` (default `application/octet-stream`; printable ASCII, the
579
+ * node refuses another with `400`) is sent as the upload's `Content-Type`:
580
+ * a component app records it and serves downloads with it. A component
581
+ * app holding as many blobs as it may answers `429 QUOTA_EXCEEDED`.
582
+ */
583
+ uploadBlob(app, data, contentType = {}) {
584
+ const mime = (typeof contentType === 'string' ? contentType : contentType.contentType) ?? 'application/octet-stream';
585
+ return this.json('POST', `apps/${seg(app)}/blobs`, undefined, data, mime);
541
586
  }
542
587
  /** One of `app`'s blobs, by id (base58 or hex). */
543
588
  async downloadBlob(app, id) {
544
- const response = await this.send('GET', `apps/${seg(app)}/blobs/${seg(id)}`);
545
- return new Uint8Array(await response.arrayBuffer());
589
+ return (await this.blobContent(app, id)).data;
590
+ }
591
+ /**
592
+ * Download a component app's blob with a grant one of its runs handed out
593
+ * (`BlobRef::grant`, up to 24 h). No session needed: the grant is the
594
+ * permission. A bad or expired grant answers `NotFound`, as a missing blob.
595
+ */
596
+ async downloadBlobGranted(app, id, grant) {
597
+ return (await this.blobContent(app, id, { grant })).data;
598
+ }
599
+ /**
600
+ * {@link downloadBlob} (or, with `grant`, {@link downloadBlobGranted}) with
601
+ * the content type the node serves it as: the uploader's, for a component
602
+ * app; `application/octet-stream` otherwise.
603
+ */
604
+ async blobContent(app, id, options = {}) {
605
+ const query = options.grant === undefined ? '' : `?grant=${encodeURIComponent(options.grant)}`;
606
+ const response = await this.send('GET', `apps/${seg(app)}/blobs/${seg(id)}${query}`);
607
+ return {
608
+ data: new Uint8Array(await response.arrayBuffer()),
609
+ contentType: response.headers.get('content-type') ?? 'application/octet-stream',
610
+ };
546
611
  }
547
612
  /** `app`'s blobs. */
548
613
  async listBlobs(app) {
@@ -606,6 +671,15 @@ export class CalimeroClient {
606
671
  async setAppMetadata(app, metadata) {
607
672
  await this.post(`apps/${seg(app)}/metadata`, { metadata });
608
673
  }
674
+ /**
675
+ * A component app's xcalls: how many messages wait in its outbox, and the
676
+ * dead letters (never delivered), newest last. Its owner's or an org
677
+ * admin's (`403` to anyone else); `409 NOT_SUPPORTED` for an app on the
678
+ * wasmer runtime, whose xcalls are follow-ups.
679
+ */
680
+ appXcalls(app) {
681
+ return this.get(`apps/${seg(app)}/xcalls`);
682
+ }
609
683
  /** What the app's storage holds: `{ keys, bytes, blobs, blobBytes }`. */
610
684
  appStorage(app) {
611
685
  return this.get(`apps/${seg(app)}/storage`);
@@ -679,7 +753,13 @@ export class CalimeroClient {
679
753
  /**
680
754
  * Create an app instance from a bundle hash, or from a package release
681
755
  * (`{ package, version?, service? }`, installed from the registry first if
682
- * the cluster lacks it).
756
+ * the cluster lacks it). With `deployment`, the instance runs the release
757
+ * that deployment offers for `package` (`version`, if given, must be it)
758
+ * and records the deployment; no install, no deployer grant. A package or
759
+ * version it does not offer answers `400 NOT_OFFERED`, several offered
760
+ * releases with no `version` `400 VERSION_REQUIRED`, and an unknown
761
+ * `service` `400 NO_SUCH_SERVICE`. A page opened through `/d/{deployment}/`
762
+ * finds its deployment with {@link pageDeployment}.
683
763
  */
684
764
  async createApp(name, bundle, options = {}) {
685
765
  const target = typeof bundle === 'string' ? { bundle } : bundle;
@@ -1046,4 +1126,81 @@ export class CalimeroClient {
1046
1126
  redeemInvite(token) {
1047
1127
  return this.post(`invites/${seg(token)}/redeem`);
1048
1128
  }
1129
+ // ---------------------------------------------------------------- deployers
1130
+ /**
1131
+ * Let an account or organization install apps where the cluster's
1132
+ * deployments are `granted`, until `until` (RFC 3339 or a `Date`; absent,
1133
+ * the grant does not end). The cluster's owner grants it, approved with
1134
+ * `passkey`, where governance says `deployers: owner`; elsewhere the
1135
+ * operator grants with its key, and this is refused `403`.
1136
+ */
1137
+ grantDeployer(deployer, passkey, options = {}) {
1138
+ const until = options.until instanceof Date ? options.until.toISOString() : options.until;
1139
+ return this.postConfirmed('deployers', { deployer, ...(until === undefined ? {} : { until }) }, passkey);
1140
+ }
1141
+ /**
1142
+ * Who may install apps on the cluster: its deployments rule, who grants
1143
+ * deployers, its owner and every grant. For the cluster's owner, or the
1144
+ * operator (its token, `useApiKey`).
1145
+ */
1146
+ deployers() {
1147
+ return this.get('deployers');
1148
+ }
1149
+ /**
1150
+ * End a deployer's grant (`deployer`, or its key `account:{id}` /
1151
+ * `org:{id}`), as the cluster's owner, approved with `passkey`. Apps it
1152
+ * installed, and the apps running them, stay.
1153
+ */
1154
+ async revokeDeployer(deployer, passkey) {
1155
+ const key = typeof deployer === 'string' ? deployer : deployerKey(deployer);
1156
+ await this.deleteConfirmed(`deployers/${seg(key)}`, passkey);
1157
+ }
1158
+ // ---------------------------------------------------------------- deployments
1159
+ /**
1160
+ * Create a deployment: `owner`'s (the caller's own account, or an
1161
+ * organization where it holds `MANAGE_APPLICATION` org-wide) list of the
1162
+ * apps it offers, named `name` (1 to 64 characters, one line) for
1163
+ * customers. Approved with `passkey`; where deployments are `granted` the
1164
+ * caller needs a deployer grant. Answers the deployment's id.
1165
+ */
1166
+ async createDeployment(name, owner, passkey) {
1167
+ const { id } = await this.postConfirmed('deployments', { name, owner }, passkey);
1168
+ return id;
1169
+ }
1170
+ /** The deployments the caller manages: its account's, and its organizations' where it holds `MANAGE_APPLICATION`. */
1171
+ async deployments() {
1172
+ const { deployments } = await this.get('deployments');
1173
+ return deployments;
1174
+ }
1175
+ /** A deployment as anyone may read it: its name, its owner's kind and name, and the apps it offers. Public. */
1176
+ deployment(id) {
1177
+ return this.get(`deployments/${seg(id)}`);
1178
+ }
1179
+ /** Delete a deployment that offers nothing and has no instances, approved with `passkey`. */
1180
+ async deleteDeployment(id, passkey) {
1181
+ await this.deleteConfirmed(`deployments/${seg(id)}`, passkey);
1182
+ }
1183
+ /**
1184
+ * Offer `package@version` (installed, and seen by the caller) as `app`
1185
+ * (1 to 64 letters, digits, `.`, `_`, `-`) in deployment `id`, or move an
1186
+ * offered app to another release of its package. Approved with `passkey`.
1187
+ */
1188
+ offerApp(id, offer, passkey) {
1189
+ return this.postConfirmed(`deployments/${seg(id)}/apps`, { ...offer }, passkey);
1190
+ }
1191
+ /**
1192
+ * Withdraw `app` from deployment `id`, approved with `passkey`; refused
1193
+ * while an instance created from the deployment still runs it.
1194
+ */
1195
+ async withdrawApp(id, app, passkey) {
1196
+ await this.deleteConfirmed(`deployments/${seg(id)}/apps/${seg(app)}`, passkey);
1197
+ }
1198
+ /**
1199
+ * The organizations with instances created from deployment `id`, for its
1200
+ * managers: each one's id, name, instance count, apps and releases, and
1201
+ * first instance. Never members, teams or app data.
1202
+ */
1203
+ deploymentOrgs(id) {
1204
+ return this.get(`deployments/${seg(id)}/orgs`);
1205
+ }
1049
1206
  }
@@ -1,9 +1,9 @@
1
- export { CalimeroClient } from './client.js';
1
+ export { CalimeroClient, deployerKey, frontendManifest, pageDeployment } from './client.js';
2
2
  export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
3
3
  export type { DirectoryFailure } from './directory.js';
4
4
  export { AppError, ArgsTooLarge, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, ReleaseDeadlineError, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
5
5
  export type { Committed } from './errors.js';
6
- export type { AppInfo, BlobInfo, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgUpgradeTarget, PresenceEntry, UpgradeRun, ViewResult, } from './client.js';
6
+ export type { AppInfo, AppXcalls, BlobContent, BlobInfo, DeadLetter, Deployer, DeployerGrant, DeployersInfo, DeploymentInfo, DeploymentOffer, DeploymentOrgs, FrontendManifest, PublicDeployment, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgUpgradeTarget, PresenceEntry, UpgradeRun, ViewResult, } from './client.js';
7
7
  export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER } from './attestation.js';
8
8
  export type { AttestationDoc, CloudPolicy, CloudReleasePolicy } from './attestation.js';
9
9
  export { browserPasskey, b64url, fromB64url } from './passkey.js';
@@ -1,4 +1,4 @@
1
- export { CalimeroClient } from './client.js';
1
+ export { CalimeroClient, deployerKey, frontendManifest, pageDeployment } from './client.js';
2
2
  export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
3
3
  export { AppError, ArgsTooLarge, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, ReleaseDeadlineError, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
4
4
  export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER } from './attestation.js';