@synnaxlabs/client 0.44.2 → 0.44.4

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.
@@ -8,7 +8,7 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
- import { caseconv, id } from "@synnaxlabs/x";
11
+ import { caseconv, id, strings } from "@synnaxlabs/x";
12
12
  import { array } from "@synnaxlabs/x/array";
13
13
  import { type CrudeTimeSpan, TimeSpan } from "@synnaxlabs/x/telem";
14
14
  import { z } from "zod";
@@ -409,7 +409,17 @@ export class Client {
409
409
  return isSingle ? res[0] : res;
410
410
  }
411
411
 
412
- async executeCommand(task: Key, type: string, args?: {}): Promise<string> {
412
+ async executeCommand(task: Key, type: string, args?: {}): Promise<string>;
413
+
414
+ async executeCommand(commands: NewCommand[]): Promise<string[]>;
415
+
416
+ async executeCommand(
417
+ task: Key | NewCommand[],
418
+ type?: string,
419
+ args?: {},
420
+ ): Promise<string | string[]> {
421
+ if (Array.isArray(task)) return await executeCommands(this.frameClient, task);
422
+ if (type == null) throw new Error("Type is required");
413
423
  return await executeCommand(this.frameClient, task, type, args);
414
424
  }
415
425
 
@@ -419,8 +429,35 @@ export class Client {
419
429
  timeout: CrudeTimeSpan,
420
430
  args?: {},
421
431
  name?: string,
432
+ statusDataZ?: StatusData,
433
+ ): Promise<Status<StatusData>>;
434
+ async executeCommandSync<StatusData extends z.ZodType = z.ZodType>(
435
+ commands: NewCommand[],
436
+ timeout: CrudeTimeSpan,
437
+ statusDataZ?: StatusData,
438
+ ): Promise<Status<StatusData>[]>;
439
+
440
+ async executeCommandSync<StatusData extends z.ZodType = z.ZodType>(
441
+ task: Key | NewCommand[],
442
+ type: string | CrudeTimeSpan,
443
+ timeout?: CrudeTimeSpan | StatusData,
444
+ args?: {},
445
+ name?: string,
422
446
  statusDataZ: StatusData = z.unknown() as unknown as StatusData,
423
- ): Promise<Status<StatusData>> {
447
+ ): Promise<Status<StatusData> | Status<StatusData>[]> {
448
+ if (Array.isArray(task)) {
449
+ const retrieveNames = async () => {
450
+ const ts = await this.retrieve({ keys: task.map((t) => t.task) });
451
+ return ts.map((t) => t.name);
452
+ };
453
+ return await executeCommandsSync(
454
+ this.frameClient,
455
+ task,
456
+ type as CrudeTimeSpan,
457
+ statusDataZ,
458
+ retrieveNames,
459
+ );
460
+ }
424
461
  const retrieveName = async () => {
425
462
  const t = await this.retrieve({ key: task });
426
463
  return t.name;
@@ -428,8 +465,8 @@ export class Client {
428
465
  return await executeCommandSync(
429
466
  this.frameClient,
430
467
  task,
431
- type,
432
- timeout,
468
+ type as string,
469
+ timeout as CrudeTimeSpan,
433
470
  name ?? retrieveName,
434
471
  statusDataZ,
435
472
  args,
@@ -444,13 +481,24 @@ const executeCommand = async (
444
481
  task: Key,
445
482
  type: string,
446
483
  args?: {},
447
- ): Promise<string> => {
484
+ ): Promise<string> => (await executeCommands(frameClient, [{ args, task, type }]))[0];
485
+
486
+ export interface NewCommand {
487
+ task: Key;
488
+ type: string;
489
+ args?: {};
490
+ }
491
+
492
+ const executeCommands = async (
493
+ frameClient: framer.Client | null,
494
+ commands: NewCommand[],
495
+ ): Promise<string[]> => {
448
496
  if (frameClient == null) throw NOT_CREATED_ERROR;
449
- const key = id.create();
450
497
  const w = await frameClient.openWriter(COMMAND_CHANNEL_NAME);
451
- await w.write(COMMAND_CHANNEL_NAME, [{ args, key, task, type }]);
498
+ const cmds = commands.map((c) => ({ ...c, key: id.create() }));
499
+ await w.write(COMMAND_CHANNEL_NAME, cmds);
452
500
  await w.close();
453
- return key;
501
+ return cmds.map((c) => c.key);
454
502
  };
455
503
 
456
504
  const executeCommandSync = async <StatusData extends z.ZodType = z.ZodType>(
@@ -461,24 +509,45 @@ const executeCommandSync = async <StatusData extends z.ZodType = z.ZodType>(
461
509
  tskName: string | (() => Promise<string>),
462
510
  statusDataZ: StatusData,
463
511
  args?: {},
464
- ): Promise<Status<StatusData>> => {
512
+ ): Promise<Status<StatusData>> =>
513
+ (
514
+ await executeCommandsSync(
515
+ frameClient,
516
+ [{ args, task, type }],
517
+ timeout,
518
+ statusDataZ,
519
+ tskName,
520
+ )
521
+ )[0];
522
+
523
+ const executeCommandsSync = async <StatusData extends z.ZodType = z.ZodType>(
524
+ frameClient: framer.Client | null,
525
+ commands: NewCommand[],
526
+ timeout: CrudeTimeSpan,
527
+ statusDataZ: StatusData,
528
+ tskName: string | string[] | (() => Promise<string | string[]>),
529
+ ): Promise<Status<StatusData>[]> => {
465
530
  if (frameClient == null) throw NOT_CREATED_ERROR;
466
531
  const streamer = await frameClient.openStreamer(STATUS_CHANNEL_NAME);
467
- const cmdKey = await executeCommand(frameClient, task, type, args);
532
+ const cmdKeys = await executeCommands(frameClient, commands);
468
533
  const parsedTimeout = new TimeSpan(timeout);
469
-
534
+ let states: Status<StatusData>[] = [];
470
535
  let timeoutID: NodeJS.Timeout | undefined;
471
536
  const timeoutPromise = new Promise<never>((_, reject) => {
472
537
  timeoutID = setTimeout(() => {
473
- void (async () =>
474
- reject(await formatTimeoutError(type, tskName, parsedTimeout, task)))();
538
+ void (async () => {
539
+ const taskKeys = commands.map((c) => c.task);
540
+ reject(await formatTimeoutError("command", tskName, parsedTimeout, taskKeys));
541
+ })();
475
542
  }, parsedTimeout.milliseconds);
476
543
  });
477
544
  try {
478
545
  while (true) {
479
546
  const frame = await Promise.race([streamer.read(), timeoutPromise]);
480
547
  const state = statusZ(statusDataZ).parse(frame.at(-1)[STATUS_CHANNEL_NAME]);
481
- if (state.key === cmdKey) return state;
548
+ if (!cmdKeys.includes(state.key)) continue;
549
+ states = [...states.filter((s) => s.key !== state.key), state];
550
+ if (states.length === cmdKeys.length) return states;
482
551
  }
483
552
  } finally {
484
553
  clearTimeout(timeoutID);
@@ -488,21 +557,25 @@ const executeCommandSync = async <StatusData extends z.ZodType = z.ZodType>(
488
557
 
489
558
  const formatTimeoutError = async (
490
559
  type: string,
491
- name: string | (() => Promise<string>),
560
+ name: string | string[] | (() => Promise<string | string[]>),
492
561
  timeout: TimeSpan,
493
- key: Key,
562
+ key: Key | Key[],
494
563
  ): Promise<Error> => {
495
564
  const formattedType = caseconv.capitalize(type);
496
565
  const formattedTimeout = timeout.toString();
497
566
  try {
498
- const name_ = typeof name === "string" ? name : await name();
567
+ let names: string[];
568
+ if (typeof name === "string") names = [name];
569
+ else if (Array.isArray(name)) names = name;
570
+ else names = array.toArray(await name());
571
+ const formattedName = strings.naturalLanguageJoin(names);
499
572
  return new Error(
500
- `${formattedType} command to ${name_} timed out after ${formattedTimeout}`,
573
+ `${formattedType} command to ${formattedName} timed out after ${formattedTimeout}`,
501
574
  );
502
575
  } catch (e) {
503
576
  console.error("Failed to retrieve task name for timeout error:", e);
504
577
  return new Error(
505
- `${formattedType} command to task with key ${key} timed out after ${formattedTimeout}`,
578
+ `${formattedType} command to task with key ${strings.naturalLanguageJoin(key)} timed out after ${formattedTimeout}`,
506
579
  );
507
580
  }
508
581
  };
@@ -14,6 +14,9 @@ import { Group } from "@/ontology/group/group";
14
14
  import { type Key, type Name, type Payload } from "@/ontology/group/payload";
15
15
  import { Writer } from "@/ontology/group/writer";
16
16
 
17
+ export const SET_CHANNEL_NAME = "sy_group_set";
18
+ export const DELETE_CHANNEL_NAME = "sy_group_delete";
19
+
17
20
  export class Client {
18
21
  private readonly creator: Writer;
19
22
 
@@ -23,10 +23,7 @@ const resolveReqZ = z.object({ range: keyZ, aliases: z.string().array() });
23
23
 
24
24
  const resolveResZ = z.object({ aliases: z.record(z.string(), channel.keyZ) });
25
25
 
26
- const setReqZ = z.object({
27
- range: keyZ,
28
- aliases: z.record(channel.keyZ.or(z.string()), z.string()),
29
- });
26
+ const setReqZ = z.object({ range: keyZ, aliases: z.record(channel.keyZ, z.string()) });
30
27
 
31
28
  const setResZ = z.unknown();
32
29
 
@@ -7,6 +7,8 @@
7
7
  // License, use of this software will be governed by the Apache License, Version 2.0,
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
+ import { TimeSpan } from "@synnaxlabs/x";
11
+
10
12
  import Synnax, { type SynnaxProps } from "@/client";
11
13
 
12
14
  export const TEST_CLIENT_PROPS: SynnaxProps = {
@@ -14,6 +16,11 @@ export const TEST_CLIENT_PROPS: SynnaxProps = {
14
16
  port: 9090,
15
17
  username: "synnax",
16
18
  password: "seldon",
19
+ retry: {
20
+ maxRetries: 4,
21
+ baseInterval: TimeSpan.seconds(1),
22
+ scale: 1.5,
23
+ },
17
24
  };
18
25
 
19
26
  export const createTestClient = (props?: Partial<SynnaxProps>): Synnax =>