@aperture-browser/api-client 0.0.0-init.0 → 0.0.0-nightly.82.2e4f6368108f

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/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { t as AuthApi } from "./service-riqZPQHq.js";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Layer from "effect/Layer";
3
4
  import * as HttpBody from "effect/unstable/http/HttpBody";
@@ -10,6 +11,9 @@ import * as PubSub from "effect/PubSub";
10
11
  import * as Stream from "effect/Stream";
11
12
  import * as Function from "effect/Function";
12
13
  import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
14
+ import * as Option from "effect/Option";
15
+ import * as SchemaTransformation from "effect/SchemaTransformation";
16
+ import * as Random from "effect/Random";
13
17
  //#region src/authorization/service.ts
14
18
  const TENANT_HEADER = "X-Aperture-Tenant-Id";
15
19
  /** The browser's own login session, sent as a cookie. */
@@ -173,10 +177,6 @@ const TOTPEnrollment = Schema.Struct({
173
177
  });
174
178
  const RecoveryCodes = Schema.Struct({ recoveryCodes: Schema.Array(Schema.String) });
175
179
  //#endregion
176
- //#region src/auth/service.ts
177
- /** Browser login, account security, and the current principal. */
178
- var AuthApi = class extends Context.Service()("@aperture-browser/api-client/AuthApi") {};
179
- //#endregion
180
180
  //#region src/auth/layer.ts
181
181
  const jsonBody = (body) => ({ body: HttpBody.jsonUnsafe(body) });
182
182
  const makeAuthApi = Effect.gen(function* () {
@@ -292,6 +292,13 @@ const ApiErrorBody = Schema.Struct({ error: Schema.Struct({
292
292
  message: Schema.String
293
293
  }) });
294
294
  /**
295
+ * The error body of a live-session route that failed inside the running session. It carries
296
+ * no stable code, so the resulting ApiRequestError uses `live_session_error` and callers
297
+ * branch on its status.
298
+ */
299
+ const LiveSessionErrorBody = Schema.Struct({ error: Schema.String });
300
+ const ErrorResponseBody = Schema.Union([ApiErrorBody, LiveSessionErrorBody]);
301
+ /**
295
302
  * A failed API request. `code` and `message` come from the server's error body when it
296
303
  * sent one; `status` is 0 when no HTTP status applies.
297
304
  */
@@ -315,7 +322,11 @@ const networkError = new ApiRequestError({
315
322
  * message of the server's error body.
316
323
  */
317
324
  const toApiRequestError = (self) => self.pipe(Effect.catchReasons("HttpClientError", {
318
- StatusCodeError: ({ response }) => HttpClientResponse.schemaBodyJson(ApiErrorBody)(response).pipe(Effect.map(({ error }) => new ApiRequestError({
325
+ StatusCodeError: ({ response }) => HttpClientResponse.schemaBodyJson(ErrorResponseBody)(response).pipe(Effect.map(({ error }) => typeof error === "string" ? new ApiRequestError({
326
+ code: "live_session_error",
327
+ message: error,
328
+ status: response.status
329
+ }) : new ApiRequestError({
319
330
  ...error,
320
331
  status: response.status
321
332
  })), Effect.orElseSucceed(() => new ApiRequestError({
@@ -359,6 +370,43 @@ const makeApiAuthorization = Effect.gen(function* () {
359
370
  });
360
371
  const apiAuthorizationLayer = Layer.effect(ApiAuthorization, makeApiAuthorization);
361
372
  //#endregion
373
+ //#region src/events/service.ts
374
+ /** The tenant's resource event log, and the deployment-wide audit log. */
375
+ var EventsApi = class extends Context.Service()("@aperture-browser/api-client/EventsApi") {};
376
+ //#endregion
377
+ //#region src/health/service.ts
378
+ /** The instance's unauthenticated health check. */
379
+ var HealthApi = class extends Context.Service()("@aperture-browser/api-client/HealthApi") {};
380
+ //#endregion
381
+ //#region src/pagination.ts
382
+ function getNextPageParam(page) {
383
+ return page.meta.hasMore ? page.meta.nextCursor : void 0;
384
+ }
385
+ function flattenInfinitePages(pages) {
386
+ return pages?.flatMap((page) => page.data) ?? [];
387
+ }
388
+ /**
389
+ * Derives `stream` and `listAll` from the call that fetches one page. That call must also
390
+ * accept a bare cursor, which every filter allows because all its fields are optional.
391
+ */
392
+ function paginated(list) {
393
+ const stream = (credentials, filter) => Stream.paginate(void 0, (cursor) => list(credentials, filter === void 0 ? { cursor } : {
394
+ ...filter,
395
+ cursor
396
+ }).pipe(Effect.map((page) => {
397
+ const next = getNextPageParam(page);
398
+ return [page.data, next === void 0 ? Option.none() : Option.some(next)];
399
+ })));
400
+ const listAll = (credentials, filter) => Stream.runCollect(stream(credentials, filter));
401
+ return {
402
+ list,
403
+ stream,
404
+ listAll
405
+ };
406
+ }
407
+ const defaultListLimit = 50;
408
+ const listQueryDefaults = { refetchOnWindowFocus: true };
409
+ //#endregion
362
410
  //#region src/query.ts
363
411
  /** Encodes tag filters as the parallel tagKey, tagOperator and tagValue query lists. */
364
412
  const tagQuery = (tags) => ({
@@ -374,10 +422,6 @@ const compactQuery = (query) => Object.fromEntries(Object.entries(query).flatMap
374
422
  return items.length > 0 ? [[key, items]] : [];
375
423
  }));
376
424
  //#endregion
377
- //#region src/events/service.ts
378
- /** The tenant's resource event log. */
379
- var EventsApi = class extends Context.Service()("@aperture-browser/api-client/EventsApi") {};
380
- //#endregion
381
425
  //#region src/events/layer.ts
382
426
  const makeEventsApi = Effect.gen(function* () {
383
427
  const httpClient = yield* HttpClient.HttpClient;
@@ -391,10 +435,43 @@ const makeEventsApi = Effect.gen(function* () {
391
435
  resourceId: params.resourceId
392
436
  }) }).pipe(authorize(Authorization.tenantScoped(credentials)));
393
437
  });
394
- return EventsApi.of({ listEvents });
438
+ const listAuditEvents = Effect.fn("EventsApi.listAuditEvents")(function* (credentials, params = {}) {
439
+ return yield* api.listAuditEvents({ params: compactQuery({
440
+ limit: params.limit,
441
+ cursor: params.cursor,
442
+ tenantId: params.tenantId,
443
+ actorType: params.actorType,
444
+ actorId: params.actorId,
445
+ action: params.action,
446
+ resourceType: params.resourceType,
447
+ resourceId: params.resourceId
448
+ }) }).pipe(authorize(Authorization.of(credentials)));
449
+ });
450
+ const events = paginated(listEvents);
451
+ const auditEvents = paginated(listAuditEvents);
452
+ return EventsApi.of({
453
+ listEvents,
454
+ streamEvents: events.stream,
455
+ listAllEvents: events.listAll,
456
+ listAuditEvents,
457
+ streamAuditEvents: auditEvents.stream,
458
+ listAllAuditEvents: auditEvents.listAll
459
+ });
395
460
  });
396
461
  const eventsApiLayer = Layer.effect(EventsApi, makeEventsApi);
397
462
  //#endregion
463
+ //#region src/health/layer.ts
464
+ const makeHealthApi = Effect.gen(function* () {
465
+ const httpClient = yield* HttpClient.HttpClient;
466
+ const { authorize } = yield* ApiAuthorization;
467
+ const api = Api.make(httpClient);
468
+ const getHealth = Effect.fn("HealthApi.getHealth")(function* () {
469
+ return yield* api.getHealth(void 0).pipe(authorize(Authorization.anonymous));
470
+ });
471
+ return HealthApi.of({ getHealth });
472
+ });
473
+ const healthApiLayer = Layer.effect(HealthApi, makeHealthApi);
474
+ //#endregion
398
475
  //#region src/sessions/schemas.ts
399
476
  const positiveInt = Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0));
400
477
  const emptyArray = Effect.succeed([]);
@@ -424,8 +501,7 @@ const BrowserStatus = Schema.Struct({
424
501
  })
425
502
  })).pipe(Schema.withDecodingDefaultKey(emptyArray))
426
503
  });
427
- /** A recording as the live session reports it. */
428
- const Recording = Schema.Struct({
504
+ const recordingFields = {
429
505
  recordingId: Schema.String,
430
506
  mode: Schema.Literals(["tab", "viewer"]),
431
507
  targetId: Schema.String,
@@ -437,21 +513,75 @@ const Recording = Schema.Struct({
437
513
  "failed"
438
514
  ]),
439
515
  stopReason: Schema.optionalKey(Schema.String),
440
- path: Schema.String,
516
+ sandboxPath: Schema.optionalKey(Schema.String),
517
+ /** @deprecated Read `relativePath`. Sessions started before it existed send only this. */
518
+ path: Schema.optionalKey(Schema.String),
441
519
  startedAt: Schema.String,
442
520
  stoppedAt: Schema.optionalKey(Schema.String),
443
521
  sizeBytes: Schema.optionalKey(Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))),
444
522
  fps: positiveInt,
445
523
  bitrateKbps: positiveInt,
446
524
  codec: Schema.String
447
- });
525
+ };
526
+ /**
527
+ * A recording as the live session reports it. Sessions that are still running an
528
+ * older wrapper send only `path`, which decoding carries over into `relativePath`.
529
+ */
530
+ const Recording = Schema.Struct({
531
+ ...recordingFields,
532
+ relativePath: Schema.optionalKey(Schema.String)
533
+ }).pipe(Schema.decodeTo(Schema.Struct({
534
+ ...recordingFields,
535
+ relativePath: Schema.String
536
+ }), SchemaTransformation.transform({
537
+ decode: ({ relativePath, ...recording }) => ({
538
+ ...recording,
539
+ relativePath: relativePath ?? recording.path ?? ""
540
+ }),
541
+ encode: (recording) => recording
542
+ })));
448
543
  //#endregion
449
544
  //#region src/sessions/service.ts
450
545
  /** Browser sessions, their live browser, and their recordings. */
451
546
  var SessionsApi = class extends Context.Service()("@aperture-browser/api-client/SessionsApi") {};
452
547
  //#endregion
548
+ //#region src/sessions/uploads.ts
549
+ const encoder = new TextEncoder();
550
+ const quotedFilename = (name) => name.replaceAll("\"", "%22").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
551
+ const contentStream = (content) => {
552
+ if (content instanceof Blob) return Stream.fromReadableStream({
553
+ evaluate: () => content.stream(),
554
+ onError: (cause) => cause
555
+ });
556
+ if (content instanceof Uint8Array) return Stream.succeed(content);
557
+ return content;
558
+ };
559
+ const streamedMultipart = Effect.fn("SessionsApi.streamedMultipart")(function* (files) {
560
+ const boundary = `aperture-upload-${(yield* Random.nextIntBetween(0, Number.MAX_SAFE_INTEGER)).toString(36)}`;
561
+ const parts = files.map((file) => {
562
+ const header = `--${boundary}\r\nContent-Disposition: form-data; name="files"; filename="${quotedFilename(file.name)}"\r\nContent-Type: application/octet-stream\r
563
+ \r
564
+ `;
565
+ return Stream.succeed(encoder.encode(header)).pipe(Stream.concat(contentStream(file.content)), Stream.concat(Stream.succeed(encoder.encode("\r\n"))));
566
+ });
567
+ const body = Stream.fromIterable(parts).pipe(Stream.flatten(), Stream.concat(Stream.succeed(encoder.encode(`--${boundary}--\r\n`))));
568
+ return HttpBody.stream(body, `multipart/form-data; boundary=${boundary}`);
569
+ });
570
+ /**
571
+ * The multipart body of an upload. FormData works with every fetch, so it is used unless a
572
+ * content is a stream, which needs the body itself to be streamed.
573
+ */
574
+ const uploadBody = Effect.fn("SessionsApi.uploadBody")(function* (files) {
575
+ const form = new FormData();
576
+ for (const file of files) if (file.content instanceof Blob) form.append("files", file.content, file.name);
577
+ else if (file.content instanceof Uint8Array) form.append("files", new Blob([new Uint8Array(file.content)]), file.name);
578
+ else return yield* streamedMultipart(files);
579
+ return HttpBody.formData(form);
580
+ });
581
+ //#endregion
453
582
  //#region src/sessions/layer.ts
454
583
  const contentDispositionFilename = (header) => header?.match(/filename="([^"]+)"/)?.[1] ?? null;
584
+ const recordingContentPath = (sessionId, recordingId) => `/sessions/${encodeURIComponent(sessionId)}/recordings/${encodeURIComponent(recordingId)}/content`;
455
585
  const makeSessionsApi = Effect.gen(function* () {
456
586
  const httpClient = yield* HttpClient.HttpClient;
457
587
  const { authorize } = yield* ApiAuthorization;
@@ -467,6 +597,7 @@ const makeSessionsApi = Effect.gen(function* () {
467
597
  ...tagQuery(params.tags)
468
598
  }) }).pipe(tenantScoped(credentials));
469
599
  });
600
+ const sessions = paginated(listSessions);
470
601
  const getSession = Effect.fn("SessionsApi.getSession")(function* (credentials, sessionId) {
471
602
  return yield* api.getSession(sessionId, void 0).pipe(tenantScoped(credentials));
472
603
  });
@@ -485,7 +616,8 @@ const makeSessionsApi = Effect.gen(function* () {
485
616
  },
486
617
  initialTargets: input.initialTargets ?? [],
487
618
  ...input.storageState === void 0 ? {} : { storageState: input.storageState },
488
- tags: input.tags ?? {}
619
+ tags: input.tags ?? {},
620
+ ...input.proxy === void 0 ? {} : { proxy: input.proxy }
489
621
  }
490
622
  }).pipe(tenantScoped(credentials));
491
623
  });
@@ -507,6 +639,9 @@ const makeSessionsApi = Effect.gen(function* () {
507
639
  const replaceSessionTags = Effect.fn("SessionsApi.replaceSessionTags")(function* (credentials, sessionId, tags) {
508
640
  return yield* api.replaceSessionTags(sessionId, { payload: { tags } }).pipe(tenantScoped(credentials));
509
641
  });
642
+ const updateSessionProxy = Effect.fn("SessionsApi.updateSessionProxy")(function* (credentials, sessionId, proxy) {
643
+ return yield* api.updateSessionProxy(sessionId, { payload: proxy }).pipe(tenantScoped(credentials));
644
+ });
510
645
  const promoteSession = Effect.fn("SessionsApi.promoteSession")(function* (credentials, sessionId, input) {
511
646
  return yield* api.promoteSession(sessionId, { payload: {
512
647
  name: input.name,
@@ -515,17 +650,45 @@ const makeSessionsApi = Effect.gen(function* () {
515
650
  tags: input.tags ?? {}
516
651
  } }).pipe(tenantScoped(credentials));
517
652
  });
653
+ const getSessionCursor = Effect.fn("SessionsApi.getSessionCursor")(function* (credentials, sessionId) {
654
+ return yield* api.getSessionCursor(sessionId, void 0).pipe(tenantScoped(credentials));
655
+ });
656
+ const setSessionCursor = Effect.fn("SessionsApi.setSessionCursor")(function* (credentials, sessionId, visible) {
657
+ return yield* api.setSessionCursor(sessionId, { payload: { visible } }).pipe(tenantScoped(credentials));
658
+ });
659
+ const listSessionRecordings = Effect.fn("SessionsApi.listSessionRecordings")(function* (credentials, sessionId) {
660
+ return yield* api.listSessionRecordings(sessionId, void 0).pipe(tenantScoped(credentials));
661
+ });
662
+ const createSessionRecording = Effect.fn("SessionsApi.createSessionRecording")(function* (credentials, sessionId, input) {
663
+ return yield* api.createSessionRecording(sessionId, { payload: input }).pipe(tenantScoped(credentials));
664
+ });
665
+ const getSessionRecording = Effect.fn("SessionsApi.getSessionRecording")(function* (credentials, sessionId, recordingId) {
666
+ return yield* api.getSessionRecording(sessionId, recordingId, void 0).pipe(tenantScoped(credentials));
667
+ });
668
+ const retargetSessionRecording = Effect.fn("SessionsApi.retargetSessionRecording")(function* (credentials, sessionId, recordingId, targetId) {
669
+ return yield* api.retargetSessionRecording(sessionId, recordingId, { payload: { targetId } }).pipe(tenantScoped(credentials));
670
+ });
671
+ const stopSessionRecording = Effect.fn("SessionsApi.stopSessionRecording")(function* (credentials, sessionId, recordingId) {
672
+ return yield* api.stopSessionRecording(sessionId, recordingId, void 0).pipe(tenantScoped(credentials));
673
+ });
674
+ const listSessionFiles = Effect.fn("SessionsApi.listSessionFiles")(function* (credentials, sessionId) {
675
+ return yield* api.listSessionFiles(sessionId, void 0).pipe(tenantScoped(credentials));
676
+ });
677
+ const createSessionFileDownloadURL = Effect.fn("SessionsApi.createSessionFileDownloadURL")(function* (credentials, sessionId, input) {
678
+ return yield* api.createSessionFileDownloadURL(sessionId, { payload: input }).pipe(tenantScoped(credentials));
679
+ });
518
680
  const getBrowserChannels = Effect.fn("SessionsApi.getBrowserChannels")(function* (credentials) {
519
681
  return yield* api.listBrowserChannels(void 0).pipe(tenantScoped(credentials));
520
682
  });
521
683
  const getBrowserStatus = Effect.fn("SessionsApi.getBrowserStatus")(function* (credentials, sessionId, sessionToken) {
522
684
  return yield* http.get(`/sessions/${encodeURIComponent(sessionId)}/browser/status`).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(BrowserStatus)), authorize({
523
685
  credentials,
524
- bearerToken: sessionToken
686
+ bearerToken: sessionToken,
687
+ tenantHeader: "tenant-scoped"
525
688
  }));
526
689
  });
527
690
  const downloadSessionRecording = Effect.fn("SessionsApi.downloadSessionRecording")(function* (credentials, sessionId, recordingId, sessionToken) {
528
- return yield* http.get(`/sessions/${encodeURIComponent(sessionId)}/recordings/${encodeURIComponent(recordingId)}/content`).pipe(Effect.flatMap((response) => Effect.map(response.arrayBuffer, (body) => ({
691
+ return yield* http.get(recordingContentPath(sessionId, recordingId)).pipe(Effect.flatMap((response) => Effect.map(response.arrayBuffer, (body) => ({
529
692
  blob: new Blob([body], { type: response.headers["content-type"] ?? "" }),
530
693
  filename: contentDispositionFilename(response.headers["content-disposition"])
531
694
  }))), authorize({
@@ -534,8 +697,49 @@ const makeSessionsApi = Effect.gen(function* () {
534
697
  tenantHeader: "tenant-scoped"
535
698
  }));
536
699
  });
700
+ const streamSessionRecording = (credentials, sessionId, recordingId, sessionToken) => http.get(recordingContentPath(sessionId, recordingId)).pipe(authorize({
701
+ credentials,
702
+ bearerToken: sessionToken,
703
+ tenantHeader: "tenant-scoped"
704
+ }), Effect.map((response) => response.stream.pipe(Stream.catchTag("HttpClientError", (error) => Stream.fromEffect(toApiRequestError(Effect.fail(error)))))), Stream.unwrap);
705
+ const uploadSessionFiles = Effect.fn("SessionsApi.uploadSessionFiles")(function* (credentials, sessionId, files, options = {}) {
706
+ const body = yield* uploadBody(files);
707
+ return (yield* http.post(`/api/sessions/${encodeURIComponent(sessionId)}/files`, {
708
+ body,
709
+ urlParams: compactQuery({ directory: options.directory })
710
+ }).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Api.UploadSessionFiles201)), tenantScoped(credentials))).files;
711
+ });
712
+ const deleteSessionFile = Effect.fn("SessionsApi.deleteSessionFile")(function* (credentials, sessionId, relativePath, options = {}) {
713
+ yield* api.deleteSessionFile(sessionId, { params: compactQuery({
714
+ relativePath,
715
+ recursive: options.recursive
716
+ }) }).pipe(tenantScoped(credentials));
717
+ });
718
+ const createSessionDirectory = Effect.fn("SessionsApi.createSessionDirectory")(function* (credentials, sessionId, relativePath) {
719
+ return yield* api.createSessionDirectory(sessionId, { payload: { relativePath } }).pipe(tenantScoped(credentials));
720
+ });
721
+ const moveSessionFile = Effect.fn("SessionsApi.moveSessionFile")(function* (credentials, sessionId, input) {
722
+ return yield* api.moveSessionFile(sessionId, { payload: input }).pipe(tenantScoped(credentials));
723
+ });
724
+ const uploadLiveSessionFiles = Effect.fn("SessionsApi.uploadLiveSessionFiles")(function* (credentials, sessionId, files, sessionToken) {
725
+ const body = yield* uploadBody(files);
726
+ return (yield* http.post(`/sessions/${encodeURIComponent(sessionId)}/uploads`, { body }).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(Api.UploadLiveSessionFiles201)), authorize({
727
+ credentials,
728
+ bearerToken: sessionToken,
729
+ tenantHeader: "tenant-scoped"
730
+ }))).files;
731
+ });
732
+ const setSessionViewport = Effect.fn("SessionsApi.setSessionViewport")(function* (credentials, sessionId, input, sessionToken) {
733
+ return yield* api.setSessionViewport(sessionId, { payload: input }).pipe(authorize({
734
+ credentials,
735
+ bearerToken: sessionToken,
736
+ tenantHeader: "tenant-scoped"
737
+ }));
738
+ });
537
739
  return SessionsApi.of({
538
740
  listSessions,
741
+ streamSessions: sessions.stream,
742
+ listAllSessions: sessions.listAll,
539
743
  getSession,
540
744
  getSessionsBulk,
541
745
  createSession,
@@ -545,10 +749,27 @@ const makeSessionsApi = Effect.gen(function* () {
545
749
  rotateSessionToken,
546
750
  rotateCollaborationCapability,
547
751
  replaceSessionTags,
752
+ updateSessionProxy,
548
753
  promoteSession,
754
+ getSessionCursor,
755
+ setSessionCursor,
756
+ listSessionRecordings,
757
+ createSessionRecording,
758
+ getSessionRecording,
759
+ retargetSessionRecording,
760
+ stopSessionRecording,
761
+ listSessionFiles,
762
+ createSessionFileDownloadURL,
549
763
  getBrowserChannels,
550
764
  getBrowserStatus,
551
- downloadSessionRecording
765
+ downloadSessionRecording,
766
+ streamSessionRecording,
767
+ uploadSessionFiles,
768
+ uploadLiveSessionFiles,
769
+ deleteSessionFile,
770
+ moveSessionFile,
771
+ createSessionDirectory,
772
+ setSessionViewport
552
773
  });
553
774
  });
554
775
  const sessionsApiLayer = Layer.effect(SessionsApi, makeSessionsApi);
@@ -573,6 +794,13 @@ const makeSnapshotsApi = Effect.gen(function* () {
573
794
  ...tagQuery(params.tags)
574
795
  }) }).pipe(tenantScoped(credentials));
575
796
  });
797
+ const snapshots = paginated(listSnapshots);
798
+ const getSnapshotByName = Effect.fn("SnapshotsApi.getSnapshotByName")(function* (credentials, name) {
799
+ return yield* snapshots.stream(credentials, {
800
+ name,
801
+ limit: 100
802
+ }).pipe(Stream.filter((candidate) => candidate.name === name), Stream.runHead);
803
+ });
576
804
  const updateSnapshot = Effect.fn("SnapshotsApi.updateSnapshot")(function* (credentials, name, input) {
577
805
  return yield* api.updateSnapshot(name, { payload: input }).pipe(tenantScoped(credentials));
578
806
  });
@@ -587,6 +815,9 @@ const makeSnapshotsApi = Effect.gen(function* () {
587
815
  });
588
816
  return SnapshotsApi.of({
589
817
  listSnapshots,
818
+ streamSnapshots: snapshots.stream,
819
+ listAllSnapshots: snapshots.listAll,
820
+ getSnapshotByName,
590
821
  updateSnapshot,
591
822
  replaceSnapshotTags,
592
823
  deleteSnapshot,
@@ -596,7 +827,7 @@ const makeSnapshotsApi = Effect.gen(function* () {
596
827
  const snapshotsApiLayer = Layer.effect(SnapshotsApi, makeSnapshotsApi);
597
828
  //#endregion
598
829
  //#region src/tenants/service.ts
599
- /** Tenant administration. */
830
+ /** Tenant administration, and the tenant a tenant token belongs to. */
600
831
  var TenantsApi = class extends Context.Service()("@aperture-browser/api-client/TenantsApi") {};
601
832
  //#endregion
602
833
  //#region src/tenants/layer.ts
@@ -612,6 +843,13 @@ const makeTenantsApi = Effect.gen(function* () {
612
843
  deleted: params.deleted
613
844
  }) }).pipe(authorize(Authorization.of(credentials)));
614
845
  });
846
+ const tenants = paginated(listTenants);
847
+ const getCurrentTenant = Effect.fn("TenantsApi.getCurrentTenant")(function* (credentials) {
848
+ return yield* api.getTenant(void 0).pipe(authorize(Authorization.of(credentials)));
849
+ });
850
+ const updateCurrentTenant = Effect.fn("TenantsApi.updateCurrentTenant")(function* (credentials, input) {
851
+ return yield* api.updateSelectedTenant({ payload: input }).pipe(authorize(Authorization.of(credentials)));
852
+ });
615
853
  const createTenant = Effect.fn("TenantsApi.createTenant")(function* (credentials, input) {
616
854
  return yield* api.createTenant({ payload: input }).pipe(authorize(Authorization.of(credentials)));
617
855
  });
@@ -626,10 +864,14 @@ const makeTenantsApi = Effect.gen(function* () {
626
864
  });
627
865
  return TenantsApi.of({
628
866
  listTenants,
867
+ streamTenants: tenants.stream,
868
+ listAllTenants: tenants.listAll,
629
869
  createTenant,
630
870
  updateTenant,
631
871
  deleteTenant,
632
- restoreTenant
872
+ restoreTenant,
873
+ getCurrentTenant,
874
+ updateCurrentTenant
633
875
  });
634
876
  });
635
877
  const tenantsApiLayer = Layer.effect(TenantsApi, makeTenantsApi);
@@ -654,6 +896,7 @@ const makeTokensApi = Effect.gen(function* () {
654
896
  scope: params.scope
655
897
  }) }).pipe(authorize(Authorization.of(credentials)));
656
898
  });
899
+ const adminTokens = paginated(listAdminTokens);
657
900
  const createAdminToken = Effect.fn("TokensApi.createAdminToken")(function* (credentials, input) {
658
901
  const systemAdmin = input.authorityType === "system_admin";
659
902
  return yield* api.createAdminToken({ payload: {
@@ -678,6 +921,7 @@ const makeTokensApi = Effect.gen(function* () {
678
921
  scope: params.scope
679
922
  }) }).pipe(authorize(Authorization.of(credentials)));
680
923
  });
924
+ const tenantTokens = paginated(listTenantTokens);
681
925
  const createTenantToken = Effect.fn("TokensApi.createTenantToken")(function* (credentials, input) {
682
926
  return yield* api.createTenantToken({ payload: {
683
927
  name: input.name,
@@ -692,9 +936,13 @@ const makeTokensApi = Effect.gen(function* () {
692
936
  });
693
937
  return TokensApi.of({
694
938
  listAdminTokens,
939
+ streamAdminTokens: adminTokens.stream,
940
+ listAllAdminTokens: adminTokens.listAll,
695
941
  createAdminToken,
696
942
  revokeAdminToken,
697
943
  listTenantTokens,
944
+ streamTenantTokens: tenantTokens.stream,
945
+ listAllTenantTokens: tenantTokens.listAll,
698
946
  createTenantToken,
699
947
  revokeTenantToken
700
948
  });
@@ -718,6 +966,7 @@ const makeUsersApi = Effect.gen(function* () {
718
966
  disabled: params.disabled
719
967
  }) }).pipe(authorize(Authorization.of(credentials)));
720
968
  });
969
+ const users = paginated(listUsers);
721
970
  const createUser = Effect.fn("UsersApi.createUser")(function* (credentials, input) {
722
971
  return yield* api.createUser({ payload: input }).pipe(authorize(Authorization.of(credentials)));
723
972
  });
@@ -739,6 +988,9 @@ const makeUsersApi = Effect.gen(function* () {
739
988
  const listUserMemberships = Effect.fn("UsersApi.listUserMemberships")(function* (credentials, userId) {
740
989
  return yield* api.listUserMemberships(userId, void 0).pipe(authorize(Authorization.of(credentials)));
741
990
  });
991
+ const listTenantMemberships = Effect.fn("UsersApi.listTenantMemberships")(function* (credentials, tenantId) {
992
+ return yield* api.listTenantMemberships(tenantId, void 0).pipe(authorize(Authorization.of(credentials)));
993
+ });
742
994
  const upsertTenantMembership = Effect.fn("UsersApi.upsertTenantMembership")(function* (credentials, tenantId, userId, scopes) {
743
995
  const payload = { scopes };
744
996
  return yield* api.upsertTenantMembership(tenantId, userId, { payload }).pipe(authorize(Authorization.of(credentials)));
@@ -748,6 +1000,8 @@ const makeUsersApi = Effect.gen(function* () {
748
1000
  });
749
1001
  return UsersApi.of({
750
1002
  listUsers,
1003
+ streamUsers: users.stream,
1004
+ listAllUsers: users.listAll,
751
1005
  createUser,
752
1006
  getUser,
753
1007
  updateUser,
@@ -755,6 +1009,7 @@ const makeUsersApi = Effect.gen(function* () {
755
1009
  disableUser,
756
1010
  restoreUser,
757
1011
  listUserMemberships,
1012
+ listTenantMemberships,
758
1013
  upsertTenantMembership,
759
1014
  deleteTenantMembership
760
1015
  });
@@ -766,22 +1021,104 @@ const usersApiLayer = Layer.effect(UsersApi, makeUsersApi);
766
1021
  * Every API service, sharing one ApiAuthorization. The services send requests through the
767
1022
  * HttpClient the app provides, decorated with each call's authorization.
768
1023
  */
769
- const apiClientLayer = Layer.mergeAll(authApiLayer, tenantsApiLayer, usersApiLayer, sessionsApiLayer, snapshotsApiLayer, tokensApiLayer, eventsApiLayer).pipe(Layer.provide(authorizedHttpClientLayer), Layer.provideMerge(apiAuthorizationLayer));
1024
+ const apiClientLayer = Layer.mergeAll(authApiLayer, tenantsApiLayer, usersApiLayer, sessionsApiLayer, snapshotsApiLayer, tokensApiLayer, eventsApiLayer, healthApiLayer).pipe(Layer.provide(authorizedHttpClientLayer), Layer.provideMerge(apiAuthorizationLayer));
770
1025
  const baseUrlLayer = (baseUrl) => Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, HttpClient.mapRequest(HttpClientRequest.prependUrl(baseUrl))));
771
1026
  //#endregion
772
- //#region src/pagination.ts
773
- function getNextPageParam(page) {
774
- return page.meta.hasMore ? page.meta.nextCursor : void 0;
1027
+ //#region src/client.ts
1028
+ /** Binds `credentials` as the first argument of every method of `service`. */
1029
+ function withCredentials(service, credentials) {
1030
+ const methods = Object.entries(service);
1031
+ return Object.fromEntries(methods.map(([name, method]) => [name, (...args) => method(credentials, ...args)]));
775
1032
  }
776
- function flattenInfinitePages(pages) {
777
- return pages?.flatMap((page) => page.data) ?? [];
778
- }
779
- const defaultListLimit = 50;
780
- const listQueryDefaults = { refetchOnWindowFocus: true };
1033
+ /**
1034
+ * The credentialed API services with one set of credentials bound, for callers that always
1035
+ * act as the same principal, such as a server holding one API token.
1036
+ */
1037
+ var ApertureClient = class extends Context.Service()("@aperture-browser/api-client/ApertureClient") {};
1038
+ const makeApertureClient = Effect.fn("makeApertureClient")(function* (credentials) {
1039
+ const auth = yield* AuthApi;
1040
+ return ApertureClient.of({
1041
+ sessions: withCredentials(yield* SessionsApi, credentials),
1042
+ snapshots: withCredentials(yield* SnapshotsApi, credentials),
1043
+ tenants: withCredentials(yield* TenantsApi, credentials),
1044
+ users: withCredentials(yield* UsersApi, credentials),
1045
+ tokens: withCredentials(yield* TokensApi, credentials),
1046
+ events: withCredentials(yield* EventsApi, credentials),
1047
+ auth: { getAuthMe: () => auth.getAuthMe(credentials.selectedTenantId, credentials) },
1048
+ health: yield* HealthApi
1049
+ });
1050
+ });
1051
+ /** ApertureClient acting with `credentials`, over the HttpClient the app provides. */
1052
+ const apertureClientLayer = (credentials) => Layer.effect(ApertureClient, makeApertureClient(credentials)).pipe(Layer.provide(apiClientLayer));
781
1053
  //#endregion
782
1054
  //#region src/schemas.ts
783
1055
  const SessionStatus = Api.SessionStatus;
784
1056
  //#endregion
785
- export { ApiAuthorization, ApiErrorBody, ApiRequestError, AuthApi, Authorization, BrowserStatus, EventsApi, LoginMethods, Passkey, PasskeyLoginOptions, PasskeyMutation, PasskeyRegistrationOptions, Passkeys, PasswordLoginResponse, Recording, RecoveryCodes, SecurityStatus, SessionStatus, SessionsApi, SnapshotsApi, TENANT_HEADER, TOTPEnrollment, TenantsApi, TokensApi, UsersApi, apiAuthorizationLayer, apiClientLayer, authApiLayer, authorizedHttpClientLayer, baseUrlLayer, compactQuery, defaultListLimit, eventsApiLayer, flattenInfinitePages, getNextPageParam, listQueryDefaults, makeApiAuthorization, makeAuthApi, makeEventsApi, makeSessionsApi, makeSnapshotsApi, makeTenantsApi, makeTokensApi, makeUsersApi, resolveTenantHeader, sessionsApiLayer, snapshotsApiLayer, tagQuery, tenantsApiLayer, toApiRequestError, tokensApiLayer, usersApiLayer, webSessionCredentials };
1057
+ //#region src/sessions/tree.ts
1058
+ /**
1059
+ * The top-level directories the session writes into. They always exist and cannot be
1060
+ * deleted, moved or renamed.
1061
+ */
1062
+ const sessionFileRootDirectories = [
1063
+ "downloads",
1064
+ "recordings",
1065
+ "uploads",
1066
+ "outputs"
1067
+ ];
1068
+ const emptyDirectory = () => ({
1069
+ directory: void 0,
1070
+ directories: /* @__PURE__ */ new Map(),
1071
+ files: []
1072
+ });
1073
+ function directoryAt(root, segments) {
1074
+ let directory = root;
1075
+ for (const segment of segments) {
1076
+ let child = directory.directories.get(segment);
1077
+ if (child === void 0) {
1078
+ child = emptyDirectory();
1079
+ directory.directories.set(segment, child);
1080
+ }
1081
+ directory = child;
1082
+ }
1083
+ return directory;
1084
+ }
1085
+ function toNodes(directory, parentPath) {
1086
+ const directories = [...directory.directories.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([name, child]) => {
1087
+ const relativePath = parentPath === "" ? name : `${parentPath}/${name}`;
1088
+ return {
1089
+ kind: "directory",
1090
+ name,
1091
+ relativePath,
1092
+ directory: child.directory,
1093
+ children: toNodes(child, relativePath)
1094
+ };
1095
+ });
1096
+ const files = [...directory.files].sort((left, right) => left.name.localeCompare(right.name));
1097
+ return [...directories, ...files];
1098
+ }
1099
+ /**
1100
+ * Arranges `listSessionFiles` results into a tree: directories first, then files, each
1101
+ * sorted by name. Empty directories appear because the listing includes them.
1102
+ */
1103
+ function sessionFileTree(entries) {
1104
+ const root = emptyDirectory();
1105
+ for (const entry of entries) {
1106
+ const segments = entry.relativePath.split("/");
1107
+ if (entry.type === "directory") {
1108
+ directoryAt(root, segments).directory = entry;
1109
+ continue;
1110
+ }
1111
+ const name = segments.pop() ?? entry.relativePath;
1112
+ directoryAt(root, segments).files.push({
1113
+ kind: "file",
1114
+ name,
1115
+ relativePath: entry.relativePath,
1116
+ file: entry
1117
+ });
1118
+ }
1119
+ return toNodes(root, "");
1120
+ }
1121
+ //#endregion
1122
+ export { ApertureClient, ApiAuthorization, ApiErrorBody, ApiRequestError, AuthApi, Authorization, BrowserStatus, EventsApi, HealthApi, LiveSessionErrorBody, LoginMethods, Passkey, PasskeyLoginOptions, PasskeyMutation, PasskeyRegistrationOptions, Passkeys, PasswordLoginResponse, Recording, RecoveryCodes, SecurityStatus, SessionStatus, SessionsApi, SnapshotsApi, TENANT_HEADER, TOTPEnrollment, TenantsApi, TokensApi, UsersApi, apertureClientLayer, apiAuthorizationLayer, apiClientLayer, authApiLayer, authorizedHttpClientLayer, baseUrlLayer, compactQuery, defaultListLimit, eventsApiLayer, flattenInfinitePages, getNextPageParam, healthApiLayer, listQueryDefaults, makeApertureClient, makeApiAuthorization, makeAuthApi, makeEventsApi, makeHealthApi, makeSessionsApi, makeSnapshotsApi, makeTenantsApi, makeTokensApi, makeUsersApi, paginated, resolveTenantHeader, sessionFileRootDirectories, sessionFileTree, sessionsApiLayer, snapshotsApiLayer, tagQuery, tenantsApiLayer, toApiRequestError, tokensApiLayer, usersApiLayer, webSessionCredentials, withCredentials };
786
1123
 
787
1124
  //# sourceMappingURL=index.js.map