@synnaxlabs/client 0.45.1 → 0.46.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.
- package/.turbo/turbo-build.log +7 -7
- package/dist/access/policy/client.d.ts +38 -26
- package/dist/access/policy/client.d.ts.map +1 -1
- package/dist/access/policy/payload.d.ts +22 -10
- package/dist/access/policy/payload.d.ts.map +1 -1
- package/dist/arc/client.d.ts +72 -0
- package/dist/arc/client.d.ts.map +1 -0
- package/dist/arc/external.d.ts +3 -0
- package/dist/arc/external.d.ts.map +1 -0
- package/dist/arc/index.d.ts +2 -0
- package/dist/arc/index.d.ts.map +1 -0
- package/dist/arc/payload.d.ts +155 -0
- package/dist/arc/payload.d.ts.map +1 -0
- package/dist/channel/client.d.ts +2 -3
- package/dist/channel/client.d.ts.map +1 -1
- package/dist/channel/payload.d.ts +23 -63
- package/dist/channel/payload.d.ts.map +1 -1
- package/dist/channel/retriever.d.ts.map +1 -1
- package/dist/client.cjs +44 -25
- package/dist/client.d.ts +5 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7479 -4756
- package/dist/framer/client.d.ts +2 -2
- package/dist/framer/client.d.ts.map +1 -1
- package/dist/framer/deleter.d.ts +2 -2
- package/dist/framer/frame.d.ts +2 -2
- package/dist/framer/streamer.d.ts +42 -56
- package/dist/framer/streamer.d.ts.map +1 -1
- package/dist/framer/writer.d.ts +45 -43
- package/dist/framer/writer.d.ts.map +1 -1
- package/dist/hardware/device/client.d.ts +5 -6
- package/dist/hardware/device/client.d.ts.map +1 -1
- package/dist/hardware/device/payload.d.ts +83 -18
- package/dist/hardware/device/payload.d.ts.map +1 -1
- package/dist/hardware/rack/client.d.ts +4 -5
- package/dist/hardware/rack/client.d.ts.map +1 -1
- package/dist/hardware/rack/payload.d.ts +81 -17
- package/dist/hardware/rack/payload.d.ts.map +1 -1
- package/dist/hardware/task/client.d.ts +67 -12
- package/dist/hardware/task/client.d.ts.map +1 -1
- package/dist/hardware/task/payload.d.ts +18 -51
- package/dist/hardware/task/payload.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/label/client.d.ts +14 -9
- package/dist/label/client.d.ts.map +1 -1
- package/dist/label/payload.d.ts +10 -11
- package/dist/label/payload.d.ts.map +1 -1
- package/dist/ontology/client.d.ts +7 -3
- package/dist/ontology/client.d.ts.map +1 -1
- package/dist/ontology/group/client.d.ts +45 -9
- package/dist/ontology/group/client.d.ts.map +1 -1
- package/dist/ontology/group/external.d.ts +0 -1
- package/dist/ontology/group/external.d.ts.map +1 -1
- package/dist/ontology/group/payload.d.ts +3 -1
- package/dist/ontology/group/payload.d.ts.map +1 -1
- package/dist/ontology/payload.d.ts +30 -17
- package/dist/ontology/payload.d.ts.map +1 -1
- package/dist/ontology/writer.d.ts +161 -0
- package/dist/ontology/writer.d.ts.map +1 -1
- package/dist/ranger/alias.d.ts +1 -1
- package/dist/ranger/alias.d.ts.map +1 -1
- package/dist/ranger/client.d.ts +9 -8
- package/dist/ranger/client.d.ts.map +1 -1
- package/dist/ranger/kv.d.ts.map +1 -1
- package/dist/ranger/payload.d.ts +5 -5
- package/dist/ranger/payload.d.ts.map +1 -1
- package/dist/ranger/writer.d.ts +6 -4
- package/dist/ranger/writer.d.ts.map +1 -1
- package/dist/status/client.d.ts +63 -0
- package/dist/status/client.d.ts.map +1 -0
- package/dist/status/external.d.ts +3 -0
- package/dist/status/external.d.ts.map +1 -0
- package/dist/status/index.d.ts +2 -0
- package/dist/status/index.d.ts.map +1 -0
- package/dist/status/payload.d.ts +32 -0
- package/dist/status/payload.d.ts.map +1 -0
- package/dist/status/status.spec.d.ts +2 -0
- package/dist/status/status.spec.d.ts.map +1 -0
- package/dist/user/client.d.ts.map +1 -1
- package/dist/user/retriever.d.ts.map +1 -1
- package/dist/workspace/client.d.ts +11 -6
- package/dist/workspace/client.d.ts.map +1 -1
- package/dist/workspace/lineplot/client.d.ts +28 -5
- package/dist/workspace/lineplot/client.d.ts.map +1 -1
- package/dist/workspace/lineplot/index.d.ts +1 -1
- package/dist/workspace/log/client.d.ts +28 -5
- package/dist/workspace/log/client.d.ts.map +1 -1
- package/dist/workspace/schematic/client.d.ts +35 -6
- package/dist/workspace/schematic/client.d.ts.map +1 -1
- package/dist/workspace/schematic/symbol/client.d.ts +6 -7
- package/dist/workspace/schematic/symbol/client.d.ts.map +1 -1
- package/dist/workspace/table/client.d.ts +28 -5
- package/dist/workspace/table/client.d.ts.map +1 -1
- package/examples/node/package-lock.json +30 -95
- package/examples/node/package.json +1 -1
- package/package.json +10 -10
- package/src/access/policy/client.ts +5 -6
- package/src/access/policy/payload.ts +4 -4
- package/src/access/policy/policy.spec.ts +4 -4
- package/src/arc/client.ts +117 -0
- package/src/{util/zod.ts → arc/external.ts} +2 -7
- package/src/arc/index.ts +10 -0
- package/src/arc/payload.ts +69 -0
- package/src/channel/client.ts +3 -4
- package/src/channel/payload.ts +4 -6
- package/src/channel/retriever.ts +2 -2
- package/src/client.ts +8 -2
- package/src/errors.spec.ts +1 -1
- package/src/framer/client.ts +4 -3
- package/src/framer/codec.spec.ts +1 -1
- package/src/hardware/device/client.ts +6 -8
- package/src/hardware/device/payload.ts +7 -6
- package/src/hardware/rack/client.ts +6 -8
- package/src/hardware/rack/payload.ts +2 -1
- package/src/hardware/task/client.ts +142 -100
- package/src/hardware/task/payload.ts +4 -4
- package/src/hardware/task/task.spec.ts +53 -2
- package/src/index.ts +3 -1
- package/src/label/client.ts +5 -8
- package/src/label/payload.ts +6 -11
- package/src/ontology/client.ts +3 -3
- package/src/ontology/group/client.ts +51 -18
- package/src/ontology/group/external.ts +0 -1
- package/src/ontology/group/group.spec.ts +6 -5
- package/src/ontology/group/payload.ts +5 -1
- package/src/ontology/ontology.spec.ts +88 -21
- package/src/ontology/payload.ts +15 -6
- package/src/ontology/writer.ts +3 -5
- package/src/ranger/alias.ts +2 -2
- package/src/ranger/client.ts +7 -3
- package/src/ranger/kv.ts +1 -2
- package/src/ranger/payload.ts +13 -1
- package/src/ranger/ranger.spec.ts +65 -1
- package/src/status/client.ts +129 -0
- package/src/status/external.ts +11 -0
- package/src/status/index.ts +10 -0
- package/src/status/payload.ts +35 -0
- package/src/status/status.spec.ts +352 -0
- package/src/user/client.ts +1 -2
- package/src/user/payload.ts +1 -1
- package/src/user/retriever.ts +2 -2
- package/src/user/user.spec.ts +41 -41
- package/src/workspace/client.ts +18 -15
- package/src/workspace/lineplot/client.ts +27 -13
- package/src/workspace/lineplot/index.ts +1 -1
- package/src/workspace/lineplot/lineplot.spec.ts +13 -10
- package/src/workspace/log/client.ts +26 -12
- package/src/workspace/log/log.spec.ts +13 -10
- package/src/workspace/payload.ts +1 -1
- package/src/workspace/schematic/client.ts +42 -17
- package/src/workspace/schematic/schematic.spec.ts +29 -25
- package/src/workspace/schematic/symbol/client.spec.ts +27 -24
- package/src/workspace/schematic/symbol/client.ts +6 -9
- package/src/workspace/table/client.ts +26 -12
- package/src/workspace/table/table.spec.ts +13 -10
- package/dist/ontology/group/group.d.ts +0 -10
- package/dist/ontology/group/group.d.ts.map +0 -1
- package/dist/ontology/group/writer.d.ts +0 -14
- package/dist/ontology/group/writer.d.ts.map +0 -1
- package/dist/util/zod.d.ts +0 -3
- package/dist/util/zod.d.ts.map +0 -1
- package/src/ontology/group/group.ts +0 -27
- package/src/ontology/group/writer.ts +0 -71
package/src/ranger/payload.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
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 { math } from "@synnaxlabs/x";
|
|
10
11
|
import { TimeRange } from "@synnaxlabs/x/telem";
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
|
|
@@ -23,7 +24,18 @@ export type Params = Key | Name | Keys | Names;
|
|
|
23
24
|
export const payloadZ = z.object({
|
|
24
25
|
key: keyZ,
|
|
25
26
|
name: nameZ,
|
|
26
|
-
timeRange: TimeRange.z
|
|
27
|
+
timeRange: TimeRange.z
|
|
28
|
+
.refine(({ isValid }) => isValid, {
|
|
29
|
+
error: "Time range start time must be before or equal to time range end time",
|
|
30
|
+
})
|
|
31
|
+
.refine(({ end }) => end.valueOf() <= math.MAX_INT64, {
|
|
32
|
+
error:
|
|
33
|
+
"Time range end time must be less than or equal to the maximum value of an int64",
|
|
34
|
+
})
|
|
35
|
+
.refine(({ start }) => start.valueOf() >= math.MIN_INT64, {
|
|
36
|
+
error:
|
|
37
|
+
"Time range start time must be greater than or equal to the minimum value of an int64",
|
|
38
|
+
}),
|
|
27
39
|
color: z.string().optional(),
|
|
28
40
|
labels: label.labelZ
|
|
29
41
|
.array()
|
|
@@ -7,16 +7,80 @@
|
|
|
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 { math, uuid } from "@synnaxlabs/x";
|
|
10
11
|
import { DataType, TimeSpan, TimeStamp } from "@synnaxlabs/x/telem";
|
|
11
12
|
import { describe, expect, it } from "vitest";
|
|
12
13
|
|
|
13
14
|
import { NotFoundError } from "@/errors";
|
|
14
|
-
import {
|
|
15
|
+
import { ranger } from "@/ranger";
|
|
15
16
|
import { createTestClient } from "@/testutil/client";
|
|
16
17
|
|
|
17
18
|
const client = createTestClient();
|
|
18
19
|
|
|
19
20
|
describe("Ranger", () => {
|
|
21
|
+
describe("payload", () => {
|
|
22
|
+
it("should validate the time range", () => {
|
|
23
|
+
const payload = ranger.payloadZ.parse({
|
|
24
|
+
name: "My New One Second Range",
|
|
25
|
+
key: uuid.create(),
|
|
26
|
+
timeRange: { start: 0, end: 1 },
|
|
27
|
+
});
|
|
28
|
+
expect(payload).toBeDefined();
|
|
29
|
+
expect(payload.timeRange.start.valueOf()).toBe(0n);
|
|
30
|
+
expect(payload.timeRange.end.valueOf()).toBe(1n);
|
|
31
|
+
});
|
|
32
|
+
it("should not validate the time range if it is invalid", () => {
|
|
33
|
+
const input = {
|
|
34
|
+
name: "My New One Second Range",
|
|
35
|
+
key: uuid.create(),
|
|
36
|
+
timeRange: { start: 1, end: 0 },
|
|
37
|
+
};
|
|
38
|
+
expect(() => ranger.payloadZ.parse(input)).toThrow(
|
|
39
|
+
"Time range start time must be before or equal to time range end time",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
it("should validate the time range if the end is less than or equal to the maximum value of an int64", () => {
|
|
43
|
+
const input = {
|
|
44
|
+
name: "range with end greater than max int64",
|
|
45
|
+
key: uuid.create(),
|
|
46
|
+
timeRange: { start: 1, end: math.MAX_INT64 },
|
|
47
|
+
};
|
|
48
|
+
const payload = ranger.payloadZ.parse(input);
|
|
49
|
+
expect(payload).toBeDefined();
|
|
50
|
+
expect(payload.timeRange.end.valueOf()).toBe(math.MAX_INT64);
|
|
51
|
+
});
|
|
52
|
+
it("should not validate the time range if the end is greater than the maximum value of an int64", () => {
|
|
53
|
+
const input = {
|
|
54
|
+
name: "range with end greater than max int64",
|
|
55
|
+
key: uuid.create(),
|
|
56
|
+
timeRange: { start: 1, end: math.MAX_INT64 + 1n },
|
|
57
|
+
};
|
|
58
|
+
expect(() => ranger.payloadZ.parse(input)).toThrow(
|
|
59
|
+
"Time range end time must be less than or equal to the maximum value of an int64",
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
it("should validate the time range if start is greater than the minimum value of an int64", () => {
|
|
63
|
+
const input = {
|
|
64
|
+
name: "range with start greater than min int64",
|
|
65
|
+
key: uuid.create(),
|
|
66
|
+
timeRange: { start: math.MIN_INT64, end: 0 },
|
|
67
|
+
};
|
|
68
|
+
const payload = ranger.payloadZ.parse(input);
|
|
69
|
+
expect(payload).toBeDefined();
|
|
70
|
+
expect(payload.timeRange.start.valueOf()).toBe(math.MIN_INT64);
|
|
71
|
+
expect(payload.timeRange.end.valueOf()).toBe(0n);
|
|
72
|
+
});
|
|
73
|
+
it("should not validate the time range if start is less than the minimum value of an int64", () => {
|
|
74
|
+
const input = {
|
|
75
|
+
name: "range with start less than min int64",
|
|
76
|
+
key: uuid.create(),
|
|
77
|
+
timeRange: { start: -1n * 2n ** 63n - 1n, end: 0 },
|
|
78
|
+
};
|
|
79
|
+
expect(() => ranger.payloadZ.parse(input)).toThrow(
|
|
80
|
+
"Time range start time must be greater than or equal to the minimum value of an int64",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
20
84
|
describe("create", () => {
|
|
21
85
|
it("should create a single range", async () => {
|
|
22
86
|
const timeRange = TimeStamp.now().spanRange(TimeSpan.seconds(1));
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Copyright 2025 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
|
+
import { array } from "@synnaxlabs/x/array";
|
|
12
|
+
import z from "zod";
|
|
13
|
+
|
|
14
|
+
import { label } from "@/label";
|
|
15
|
+
import { ontology } from "@/ontology";
|
|
16
|
+
import { type Key, keyZ, type New, newZ, type Status, statusZ } from "@/status/payload";
|
|
17
|
+
import { checkForMultipleOrNoResults } from "@/util/retrieve";
|
|
18
|
+
|
|
19
|
+
const setReqZ = <DetailsSchema extends z.ZodType = z.ZodNever>(
|
|
20
|
+
detailsSchema?: DetailsSchema,
|
|
21
|
+
) =>
|
|
22
|
+
z.object({
|
|
23
|
+
parent: ontology.idZ.optional(),
|
|
24
|
+
statuses: newZ(detailsSchema).array(),
|
|
25
|
+
});
|
|
26
|
+
const setResZ = <DetailsSchema extends z.ZodType = z.ZodNever>(
|
|
27
|
+
detailsSchema?: DetailsSchema,
|
|
28
|
+
) => z.object({ statuses: statusZ(detailsSchema).array() });
|
|
29
|
+
const deleteReqZ = z.object({ keys: keyZ.array() });
|
|
30
|
+
const emptyResZ = z.object({});
|
|
31
|
+
|
|
32
|
+
const SET_ENDPOINT = "/status/set";
|
|
33
|
+
const DELETE_ENDPOINT = "/status/delete";
|
|
34
|
+
const RETRIEVE_ENDPOINT = "/status/retrieve";
|
|
35
|
+
|
|
36
|
+
const retrieveRequestZ = z.object({
|
|
37
|
+
keys: keyZ.array().optional(),
|
|
38
|
+
searchTerm: z.string().optional(),
|
|
39
|
+
offset: z.number().optional(),
|
|
40
|
+
limit: z.number().optional(),
|
|
41
|
+
includeLabels: z.boolean().optional(),
|
|
42
|
+
hasLabels: label.keyZ.array().optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const singleRetrieveArgsZ = z
|
|
46
|
+
.object({ key: keyZ, includeLabels: z.boolean().optional() })
|
|
47
|
+
.transform(({ key, includeLabels }) => ({ keys: [key], includeLabels }));
|
|
48
|
+
|
|
49
|
+
const retrieveArgsZ = z.union([singleRetrieveArgsZ, retrieveRequestZ]);
|
|
50
|
+
|
|
51
|
+
export type RetrieveArgs = z.input<typeof retrieveArgsZ>;
|
|
52
|
+
export type SingleRetrieveArgs = z.input<typeof singleRetrieveArgsZ>;
|
|
53
|
+
export type MultiRetrieveArgs = z.input<typeof retrieveRequestZ>;
|
|
54
|
+
|
|
55
|
+
const retrieveResponseZ = <DetailsSchema extends z.ZodType = z.ZodNever>(
|
|
56
|
+
detailsSchema?: DetailsSchema,
|
|
57
|
+
) => z.object({ statuses: array.nullableZ(statusZ(detailsSchema)) });
|
|
58
|
+
|
|
59
|
+
export interface SetOptions {
|
|
60
|
+
parent?: ontology.ID;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class Client {
|
|
64
|
+
readonly type: string = "status";
|
|
65
|
+
private readonly client: UnaryClient;
|
|
66
|
+
|
|
67
|
+
constructor(client: UnaryClient) {
|
|
68
|
+
this.client = client;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async retrieve<DetailsSchema extends z.ZodType>(
|
|
72
|
+
args: SingleRetrieveArgs & { detailsSchema?: DetailsSchema },
|
|
73
|
+
): Promise<Status<DetailsSchema>>;
|
|
74
|
+
async retrieve(args: SingleRetrieveArgs): Promise<Status>;
|
|
75
|
+
async retrieve(args: MultiRetrieveArgs): Promise<Status[]>;
|
|
76
|
+
async retrieve<DetailsSchema extends z.ZodType = z.ZodNever>(
|
|
77
|
+
args: RetrieveArgs & { detailsSchema?: DetailsSchema },
|
|
78
|
+
): Promise<Status<DetailsSchema> | Status<DetailsSchema>[]> {
|
|
79
|
+
const isSingle = "key" in args;
|
|
80
|
+
const res = await sendRequired(
|
|
81
|
+
this.client,
|
|
82
|
+
RETRIEVE_ENDPOINT,
|
|
83
|
+
args,
|
|
84
|
+
retrieveArgsZ,
|
|
85
|
+
retrieveResponseZ<DetailsSchema>(args.detailsSchema),
|
|
86
|
+
);
|
|
87
|
+
checkForMultipleOrNoResults("Status", args, res.statuses, isSingle);
|
|
88
|
+
const statuses = res.statuses as unknown as Status<DetailsSchema>[];
|
|
89
|
+
return isSingle ? statuses[0] : statuses;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async set<DetailsSchema extends z.ZodType>(
|
|
93
|
+
status: New<DetailsSchema>,
|
|
94
|
+
opts?: SetOptions & { detailsSchema?: DetailsSchema },
|
|
95
|
+
): Promise<Status<DetailsSchema>>;
|
|
96
|
+
async set(status: New, opts?: SetOptions): Promise<Status>;
|
|
97
|
+
async set(statuses: New[], opts?: SetOptions): Promise<Status[]>;
|
|
98
|
+
async set<DetailsSchema extends z.ZodType = z.ZodNever>(
|
|
99
|
+
statuses: New<DetailsSchema> | New<DetailsSchema>[],
|
|
100
|
+
opts: SetOptions & { detailsSchema?: DetailsSchema } = {},
|
|
101
|
+
): Promise<Status<DetailsSchema> | Status<DetailsSchema>[]> {
|
|
102
|
+
const isMany = Array.isArray(statuses);
|
|
103
|
+
const res = await sendRequired<
|
|
104
|
+
ReturnType<typeof setReqZ<DetailsSchema>>,
|
|
105
|
+
ReturnType<typeof setResZ<DetailsSchema>>
|
|
106
|
+
>(
|
|
107
|
+
this.client,
|
|
108
|
+
SET_ENDPOINT,
|
|
109
|
+
{
|
|
110
|
+
statuses: array.toArray(statuses),
|
|
111
|
+
parent: opts.parent,
|
|
112
|
+
},
|
|
113
|
+
setReqZ(opts.detailsSchema),
|
|
114
|
+
setResZ(opts.detailsSchema),
|
|
115
|
+
);
|
|
116
|
+
const created = res.statuses as unknown as Status<DetailsSchema>[];
|
|
117
|
+
return isMany ? created : created[0];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async delete(keys: Key | Key[]): Promise<void> {
|
|
121
|
+
await sendRequired<typeof deleteReqZ, typeof emptyResZ>(
|
|
122
|
+
this.client,
|
|
123
|
+
DELETE_ENDPOINT,
|
|
124
|
+
{ keys: array.toArray(keys) },
|
|
125
|
+
deleteReqZ,
|
|
126
|
+
emptyResZ,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Copyright 2025 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
export * from "@/status/client";
|
|
11
|
+
export * from "@/status/payload";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright 2025 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
export * as status from "@/status/external";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Copyright 2025 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
import { status } from "@synnaxlabs/x";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import { type ontology } from "@/ontology";
|
|
14
|
+
|
|
15
|
+
export const keyZ = z.string();
|
|
16
|
+
export type Key = z.infer<typeof keyZ>;
|
|
17
|
+
|
|
18
|
+
export type Params = Key | Key[];
|
|
19
|
+
|
|
20
|
+
export const statusZ = status.statusZ;
|
|
21
|
+
|
|
22
|
+
export const newZ = <DetailsSchema extends z.ZodType = z.ZodNever>(
|
|
23
|
+
detailsSchema?: DetailsSchema,
|
|
24
|
+
) => statusZ(detailsSchema).omit({ labels: true }).partial({ key: true });
|
|
25
|
+
|
|
26
|
+
export type New<DetailsSchema extends z.ZodType = z.ZodNever> = z.input<
|
|
27
|
+
ReturnType<typeof newZ<DetailsSchema>>
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
export type Status<Details = never> = status.Status<Details>;
|
|
31
|
+
|
|
32
|
+
export const SET_CHANNEL_NAME = "sy_status_set";
|
|
33
|
+
export const DELETE_CHANNEL_NAME = "sy_status_delete";
|
|
34
|
+
|
|
35
|
+
export const ontologyID = (key: Key): ontology.ID => ({ type: "status", key });
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// Copyright 2025 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
import { TimeStamp, uuid } from "@synnaxlabs/x";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import z from "zod";
|
|
13
|
+
|
|
14
|
+
import { ontology } from "@/ontology";
|
|
15
|
+
import { group } from "@/ontology/group";
|
|
16
|
+
import { status } from "@/status";
|
|
17
|
+
import { createTestClient } from "@/testutil/client";
|
|
18
|
+
|
|
19
|
+
const client = createTestClient();
|
|
20
|
+
|
|
21
|
+
describe("Status", () => {
|
|
22
|
+
describe("set", () => {
|
|
23
|
+
it("should create a new status", async () => {
|
|
24
|
+
const s = await client.statuses.set({
|
|
25
|
+
name: "Test Status",
|
|
26
|
+
key: "test-status-1",
|
|
27
|
+
variant: "info",
|
|
28
|
+
message: "This is a test status",
|
|
29
|
+
time: TimeStamp.now(),
|
|
30
|
+
});
|
|
31
|
+
expect(s.key).toBe("test-status-1");
|
|
32
|
+
expect(s.name).toBe("Test Status");
|
|
33
|
+
expect(s.variant).toBe("info");
|
|
34
|
+
expect(s.message).toBe("This is a test status");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should update an existing status", async () => {
|
|
38
|
+
const key = "test-status-update";
|
|
39
|
+
await client.statuses.set({
|
|
40
|
+
name: "Original Status",
|
|
41
|
+
key,
|
|
42
|
+
variant: "info",
|
|
43
|
+
message: "Original message",
|
|
44
|
+
time: TimeStamp.now(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const updated = await client.statuses.set({
|
|
48
|
+
name: "Updated Status",
|
|
49
|
+
key,
|
|
50
|
+
variant: "warning",
|
|
51
|
+
message: "Updated message",
|
|
52
|
+
time: TimeStamp.now(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(updated.key).toBe(key);
|
|
56
|
+
expect(updated.name).toBe("Updated Status");
|
|
57
|
+
expect(updated.variant).toBe("warning");
|
|
58
|
+
expect(updated.message).toBe("Updated message");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should create multiple statuses at once", async () => {
|
|
62
|
+
const statuses = await client.statuses.set([
|
|
63
|
+
{
|
|
64
|
+
name: "Status 1",
|
|
65
|
+
key: "batch-1",
|
|
66
|
+
variant: "success",
|
|
67
|
+
message: "First batch status",
|
|
68
|
+
time: TimeStamp.now(),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "Status 2",
|
|
72
|
+
key: "batch-2",
|
|
73
|
+
variant: "error",
|
|
74
|
+
message: "Second batch status",
|
|
75
|
+
time: TimeStamp.now(),
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
expect(statuses).toHaveLength(2);
|
|
80
|
+
expect(statuses[0].key).toBe("batch-1");
|
|
81
|
+
expect(statuses[1].key).toBe("batch-2");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should set a status with a parent", async () => {
|
|
85
|
+
const parentGroup = await client.ontology.groups.create({
|
|
86
|
+
parent: ontology.ROOT_ID,
|
|
87
|
+
name: "Parent Group",
|
|
88
|
+
});
|
|
89
|
+
const parentOntologyID = group.ontologyID(parentGroup.key);
|
|
90
|
+
const s = await client.statuses.set(
|
|
91
|
+
{
|
|
92
|
+
name: "Child Status",
|
|
93
|
+
key: "child-status",
|
|
94
|
+
variant: "info",
|
|
95
|
+
message: "Status with parent",
|
|
96
|
+
time: TimeStamp.now(),
|
|
97
|
+
},
|
|
98
|
+
{ parent: parentOntologyID },
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(s.key).toBe("child-status");
|
|
102
|
+
|
|
103
|
+
const resources = await client.ontology.retrieveChildren(parentOntologyID);
|
|
104
|
+
|
|
105
|
+
const statusResource = resources.find((r) => r.id.key === "child-status");
|
|
106
|
+
expect(statusResource).toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("retrieve", () => {
|
|
111
|
+
it("should retrieve a status by key", async () => {
|
|
112
|
+
const created = await client.statuses.set({
|
|
113
|
+
name: "Retrieve Test",
|
|
114
|
+
key: "retrieve-test",
|
|
115
|
+
variant: "loading",
|
|
116
|
+
message: "Test retrieve",
|
|
117
|
+
time: TimeStamp.now(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const retrieved = await client.statuses.retrieve({ key: "retrieve-test" });
|
|
121
|
+
expect(retrieved.key).toBe(created.key);
|
|
122
|
+
expect(retrieved.name).toBe(created.name);
|
|
123
|
+
expect(retrieved.variant).toBe(created.variant);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should retrieve multiple statuses by keys", async () => {
|
|
127
|
+
await client.statuses.set([
|
|
128
|
+
{
|
|
129
|
+
name: "Multi 1",
|
|
130
|
+
key: "multi-1",
|
|
131
|
+
variant: "info",
|
|
132
|
+
message: "First",
|
|
133
|
+
time: TimeStamp.now(),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "Multi 2",
|
|
137
|
+
key: "multi-2",
|
|
138
|
+
variant: "warning",
|
|
139
|
+
message: "Second",
|
|
140
|
+
time: TimeStamp.now(),
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
const statuses = await client.statuses.retrieve({
|
|
145
|
+
keys: ["multi-1", "multi-2"],
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(statuses).toHaveLength(2);
|
|
149
|
+
const keys = statuses.map((s) => s.key);
|
|
150
|
+
expect(keys).toContain("multi-1");
|
|
151
|
+
expect(keys).toContain("multi-2");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should search for statuses", async () => {
|
|
155
|
+
const uniqueName = `SearchableStatus_${Date.now()}`;
|
|
156
|
+
await client.statuses.set({
|
|
157
|
+
name: uniqueName,
|
|
158
|
+
key: `searchable-${Date.now()}`,
|
|
159
|
+
variant: "info",
|
|
160
|
+
message: "Searchable status",
|
|
161
|
+
time: TimeStamp.now(),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const results = await client.statuses.retrieve({
|
|
165
|
+
searchTerm: uniqueName,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
169
|
+
expect(results.some((s) => s.name === uniqueName)).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should paginate results", async () => {
|
|
173
|
+
// Create several statuses
|
|
174
|
+
const keys = [];
|
|
175
|
+
for (let i = 0; i < 5; i++) {
|
|
176
|
+
const key = `paginate-${i}-${Date.now()}`;
|
|
177
|
+
keys.push(key);
|
|
178
|
+
await client.statuses.set({
|
|
179
|
+
name: `Paginate ${i}`,
|
|
180
|
+
key,
|
|
181
|
+
variant: "info",
|
|
182
|
+
message: `Message ${i}`,
|
|
183
|
+
time: TimeStamp.now(),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Retrieve with limit
|
|
188
|
+
const page1 = await client.statuses.retrieve({
|
|
189
|
+
keys,
|
|
190
|
+
limit: 2,
|
|
191
|
+
offset: 0,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const page2 = await client.statuses.retrieve({
|
|
195
|
+
keys,
|
|
196
|
+
limit: 2,
|
|
197
|
+
offset: 2,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(page1).toHaveLength(2);
|
|
201
|
+
expect(page2).toHaveLength(2);
|
|
202
|
+
|
|
203
|
+
// Ensure no overlap
|
|
204
|
+
const page1Keys = page1.map((s) => s.key);
|
|
205
|
+
const page2Keys = page2.map((s) => s.key);
|
|
206
|
+
expect(page1Keys.some((k) => page2Keys.includes(k))).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should retrieve a status with a details schema", async () => {
|
|
210
|
+
const detailsSchema = z.object({
|
|
211
|
+
name: z.string(),
|
|
212
|
+
key: z.string(),
|
|
213
|
+
});
|
|
214
|
+
const s = await client.statuses.set<typeof detailsSchema>({
|
|
215
|
+
name: "Details Schema",
|
|
216
|
+
key: "details-schema",
|
|
217
|
+
variant: "info",
|
|
218
|
+
message: "Test",
|
|
219
|
+
time: TimeStamp.now(),
|
|
220
|
+
details: {
|
|
221
|
+
name: "Details Schema",
|
|
222
|
+
key: "details-schema",
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
const retrieved = await client.statuses.retrieve<typeof detailsSchema>({
|
|
226
|
+
key: s.key,
|
|
227
|
+
detailsSchema,
|
|
228
|
+
});
|
|
229
|
+
expect(retrieved.key).toBe(s.key);
|
|
230
|
+
expect(retrieved.name).toBe(s.name);
|
|
231
|
+
expect(retrieved.details).toBeDefined();
|
|
232
|
+
expect(retrieved.details.name).toBe(s.details.name);
|
|
233
|
+
expect(retrieved.details.key).toBe(s.details.key);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("delete", () => {
|
|
238
|
+
it("should delete a status by key", async () => {
|
|
239
|
+
const s = await client.statuses.set({
|
|
240
|
+
name: "To Delete",
|
|
241
|
+
key: "delete-me",
|
|
242
|
+
variant: "error",
|
|
243
|
+
message: "Will be deleted",
|
|
244
|
+
time: TimeStamp.now(),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await client.statuses.delete(s.key);
|
|
248
|
+
|
|
249
|
+
await expect(
|
|
250
|
+
async () => await client.statuses.retrieve({ key: s.key }),
|
|
251
|
+
).rejects.toThrow();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should delete multiple statuses", async () => {
|
|
255
|
+
const keys = ["del-1", "del-2", "del-3"];
|
|
256
|
+
await client.statuses.set(
|
|
257
|
+
keys.map((key) => ({
|
|
258
|
+
name: `Delete ${key}`,
|
|
259
|
+
key,
|
|
260
|
+
variant: "info" as status.Status["variant"],
|
|
261
|
+
message: "To be deleted",
|
|
262
|
+
time: TimeStamp.now(),
|
|
263
|
+
})),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
await client.statuses.delete(keys);
|
|
267
|
+
|
|
268
|
+
// Try to retrieve them - should get empty or error
|
|
269
|
+
const results = await client.statuses.retrieve({ keys }).catch(() => []);
|
|
270
|
+
expect(results).toHaveLength(0);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("should be idempotent", async () => {
|
|
274
|
+
const key = "idempotent-delete";
|
|
275
|
+
|
|
276
|
+
// Delete a non-existent status - should not throw
|
|
277
|
+
await expect(client.statuses.delete(key)).resolves.not.toThrow();
|
|
278
|
+
|
|
279
|
+
// Create and delete
|
|
280
|
+
await client.statuses.set({
|
|
281
|
+
name: "Idempotent",
|
|
282
|
+
key,
|
|
283
|
+
variant: "info",
|
|
284
|
+
message: "Test",
|
|
285
|
+
time: TimeStamp.now(),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
await client.statuses.delete(key);
|
|
289
|
+
|
|
290
|
+
// Delete again - should not throw
|
|
291
|
+
await expect(client.statuses.delete(key)).resolves.not.toThrow();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe("with labels", () => {
|
|
296
|
+
it("should correctly retrieve a status with labels attached", async () => {
|
|
297
|
+
const label1 = await client.labels.create({
|
|
298
|
+
name: "Label 1",
|
|
299
|
+
color: "#0000FF",
|
|
300
|
+
});
|
|
301
|
+
const label2 = await client.labels.create({
|
|
302
|
+
name: "Label 2",
|
|
303
|
+
color: "#FF0000",
|
|
304
|
+
});
|
|
305
|
+
const stat = await client.statuses.set({
|
|
306
|
+
name: "Idempotent",
|
|
307
|
+
key: uuid.create(),
|
|
308
|
+
variant: "info",
|
|
309
|
+
message: "Test",
|
|
310
|
+
time: TimeStamp.now(),
|
|
311
|
+
});
|
|
312
|
+
await client.labels.label(status.ontologyID(stat.key), [label1.key, label2.key], {
|
|
313
|
+
replace: true,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const retrievedStat = await client.statuses.retrieve({
|
|
317
|
+
key: stat.key,
|
|
318
|
+
includeLabels: true,
|
|
319
|
+
});
|
|
320
|
+
expect(retrievedStat.key).toEqual(stat.key);
|
|
321
|
+
expect(retrievedStat.labels).toHaveLength(2);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe("status variants", () => {
|
|
326
|
+
it("should support all status variants", async () => {
|
|
327
|
+
const variants: status.Status["variant"][] = [
|
|
328
|
+
"success",
|
|
329
|
+
"info",
|
|
330
|
+
"warning",
|
|
331
|
+
"error",
|
|
332
|
+
"loading",
|
|
333
|
+
"disabled",
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
const statuses = await client.statuses.set(
|
|
337
|
+
variants.map((variant) => ({
|
|
338
|
+
name: `Variant ${variant}`,
|
|
339
|
+
key: `variant-${variant}-${Date.now()}`,
|
|
340
|
+
variant,
|
|
341
|
+
message: `Testing ${variant} variant`,
|
|
342
|
+
time: TimeStamp.now(),
|
|
343
|
+
})),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
expect(statuses).toHaveLength(variants.length);
|
|
347
|
+
statuses.forEach((s, i) => {
|
|
348
|
+
expect(s.variant).toBe(variants[i]);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
});
|
package/src/user/client.ts
CHANGED
|
@@ -14,7 +14,6 @@ import { z } from "zod";
|
|
|
14
14
|
import { MultipleFoundError, NotFoundError } from "@/errors";
|
|
15
15
|
import { type ontology } from "@/ontology";
|
|
16
16
|
import { type Key, keyZ, type New, newZ, type User, userZ } from "@/user/payload";
|
|
17
|
-
import { nullableArrayZ } from "@/util/zod";
|
|
18
17
|
|
|
19
18
|
const retrieveRequestZ = z.object({
|
|
20
19
|
keys: keyZ.array().optional(),
|
|
@@ -54,7 +53,7 @@ export type RetrieveArgs = z.input<typeof retrieveArgsZ>;
|
|
|
54
53
|
|
|
55
54
|
export interface RetrieveRequest extends z.infer<typeof retrieveRequestZ> {}
|
|
56
55
|
|
|
57
|
-
const retrieveResZ = z.object({ users:
|
|
56
|
+
const retrieveResZ = z.object({ users: array.nullableZ(userZ) });
|
|
58
57
|
|
|
59
58
|
const createReqZ = z.object({ users: newZ.array() });
|
|
60
59
|
const createResZ = z.object({ users: userZ.array() });
|
package/src/user/payload.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type Key = z.infer<typeof keyZ>;
|
|
|
14
14
|
|
|
15
15
|
export const userZ = z.object({
|
|
16
16
|
key: keyZ,
|
|
17
|
-
username: z.string().min(1),
|
|
17
|
+
username: z.string().min(1, "Username is required"),
|
|
18
18
|
// defaults for firstName, lastName, and rootUser are done to give compatibility with
|
|
19
19
|
// servers running v0.30.x and earlier. These defaults should be removed in a future
|
|
20
20
|
// release.
|