@synnaxlabs/client 0.25.0 → 0.26.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.
Files changed (121) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/access/access.spec.d.ts +2 -0
  3. package/dist/access/access.spec.d.ts.map +1 -0
  4. package/dist/access/client.d.ts +13 -0
  5. package/dist/access/client.d.ts.map +1 -0
  6. package/dist/access/external.d.ts +3 -0
  7. package/dist/access/external.d.ts.map +1 -0
  8. package/dist/access/index.d.ts +2 -0
  9. package/dist/access/index.d.ts.map +1 -0
  10. package/dist/access/payload.d.ts +105 -0
  11. package/dist/access/payload.d.ts.map +1 -0
  12. package/dist/auth/auth.d.ts.map +1 -1
  13. package/dist/channel/payload.d.ts +17 -14
  14. package/dist/channel/payload.d.ts.map +1 -1
  15. package/dist/channel/retriever.d.ts +7 -7
  16. package/dist/client.cjs +18 -18
  17. package/dist/client.d.ts +4 -0
  18. package/dist/client.d.ts.map +1 -1
  19. package/dist/client.js +2223 -2126
  20. package/dist/control/state.d.ts +1 -1
  21. package/dist/control/state.d.ts.map +1 -1
  22. package/dist/framer/adapter.d.ts +2 -0
  23. package/dist/framer/adapter.d.ts.map +1 -1
  24. package/dist/framer/client.d.ts.map +1 -1
  25. package/dist/framer/deleter.d.ts +1 -1
  26. package/dist/framer/deleter.d.ts.map +1 -1
  27. package/dist/framer/writer.d.ts +9 -6
  28. package/dist/framer/writer.d.ts.map +1 -1
  29. package/dist/hardware/device/client.d.ts +1 -66
  30. package/dist/hardware/device/client.d.ts.map +1 -1
  31. package/dist/hardware/device/external.d.ts +1 -0
  32. package/dist/hardware/device/external.d.ts.map +1 -1
  33. package/dist/hardware/device/index.d.ts +1 -1
  34. package/dist/hardware/device/index.d.ts.map +1 -1
  35. package/dist/hardware/device/payload.d.ts +73 -0
  36. package/dist/hardware/device/payload.d.ts.map +1 -0
  37. package/dist/hardware/rack/client.d.ts +2 -25
  38. package/dist/hardware/rack/client.d.ts.map +1 -1
  39. package/dist/hardware/rack/external.d.ts +1 -0
  40. package/dist/hardware/rack/external.d.ts.map +1 -1
  41. package/dist/hardware/rack/payload.d.ts +30 -0
  42. package/dist/hardware/rack/payload.d.ts.map +1 -0
  43. package/dist/hardware/task/client.d.ts +1 -145
  44. package/dist/hardware/task/client.d.ts.map +1 -1
  45. package/dist/hardware/task/external.d.ts +3 -0
  46. package/dist/hardware/task/external.d.ts.map +1 -0
  47. package/dist/hardware/task/index.d.ts +1 -1
  48. package/dist/hardware/task/index.d.ts.map +1 -1
  49. package/dist/hardware/task/payload.d.ts +151 -0
  50. package/dist/hardware/task/payload.d.ts.map +1 -0
  51. package/dist/label/payload.d.ts +1 -0
  52. package/dist/label/payload.d.ts.map +1 -1
  53. package/dist/ontology/client.d.ts +5 -5
  54. package/dist/ontology/payload.d.ts +40 -37
  55. package/dist/ontology/payload.d.ts.map +1 -1
  56. package/dist/ranger/payload.d.ts +5 -0
  57. package/dist/ranger/payload.d.ts.map +1 -1
  58. package/dist/setupspecs.d.ts.map +1 -1
  59. package/dist/signals/observable.d.ts +1 -1
  60. package/dist/signals/observable.d.ts.map +1 -1
  61. package/dist/user/client.d.ts +9 -0
  62. package/dist/user/client.d.ts.map +1 -0
  63. package/dist/user/external.d.ts +3 -0
  64. package/dist/user/external.d.ts.map +1 -0
  65. package/dist/user/index.d.ts +1 -1
  66. package/dist/user/index.d.ts.map +1 -1
  67. package/dist/user/payload.d.ts +5 -0
  68. package/dist/user/payload.d.ts.map +1 -1
  69. package/dist/workspace/lineplot/payload.d.ts +3 -0
  70. package/dist/workspace/lineplot/payload.d.ts.map +1 -1
  71. package/dist/workspace/payload.d.ts +3 -0
  72. package/dist/workspace/payload.d.ts.map +1 -1
  73. package/dist/workspace/schematic/payload.d.ts +3 -0
  74. package/dist/workspace/schematic/payload.d.ts.map +1 -1
  75. package/examples/node/basicReadWrite.js +28 -18
  76. package/examples/node/liveStream.js +7 -8
  77. package/examples/node/package-lock.json +2165 -2365
  78. package/examples/node/package.json +1 -1
  79. package/examples/node/seriesAndFrames.js +48 -47
  80. package/examples/node/streamWrite.js +34 -33
  81. package/package.json +5 -5
  82. package/src/access/access.spec.ts +276 -0
  83. package/src/access/client.ts +86 -0
  84. package/src/access/external.ts +11 -0
  85. package/src/access/index.ts +10 -0
  86. package/src/access/payload.ts +35 -0
  87. package/src/auth/auth.ts +1 -1
  88. package/src/channel/payload.ts +7 -0
  89. package/src/client.ts +7 -1
  90. package/src/control/state.ts +3 -3
  91. package/src/framer/adapter.spec.ts +9 -0
  92. package/src/framer/adapter.ts +12 -0
  93. package/src/framer/client.ts +3 -2
  94. package/src/framer/deleter.ts +2 -4
  95. package/src/framer/writer.spec.ts +99 -14
  96. package/src/framer/writer.ts +56 -23
  97. package/src/hardware/device/client.ts +8 -36
  98. package/src/hardware/device/external.ts +1 -0
  99. package/src/hardware/device/index.ts +1 -1
  100. package/src/hardware/device/payload.ts +44 -0
  101. package/src/hardware/rack/client.ts +10 -15
  102. package/src/hardware/rack/external.ts +1 -0
  103. package/src/hardware/rack/payload.ts +23 -0
  104. package/src/hardware/task/client.ts +1 -84
  105. package/src/hardware/task/external.ts +11 -0
  106. package/src/hardware/task/index.ts +1 -1
  107. package/src/hardware/task/payload.ts +92 -0
  108. package/src/label/payload.ts +3 -1
  109. package/src/ontology/payload.ts +6 -1
  110. package/src/ranger/payload.ts +11 -0
  111. package/src/setupspecs.ts +4 -2
  112. package/src/signals/observable.ts +5 -3
  113. package/src/transport.ts +3 -3
  114. package/src/user/client.ts +37 -0
  115. package/src/user/external.ts +11 -0
  116. package/src/user/index.ts +1 -1
  117. package/src/user/payload.ts +11 -0
  118. package/src/workspace/lineplot/payload.ts +7 -0
  119. package/src/workspace/payload.ts +7 -0
  120. package/src/workspace/schematic/payload.ts +7 -0
  121. package/tsconfig.json +4 -2
@@ -11,6 +11,6 @@
11
11
  "author": "",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@synnaxlabs/client": "^0.21.0"
14
+ "@synnaxlabs/client": "^0.26.1"
15
15
  }
16
16
  }
@@ -21,13 +21,13 @@ series = new Series({ data: [1, 2, 3, 4, 5], dataType: "float32" });
21
21
  // automatically be of type string.
22
22
  series = new Series(["apple", "banana", "cherry"]);
23
23
 
24
- // Construct a series from a Float32Array. This is the most efficient way to
25
- // construct a series from a large amount of data.
24
+ // Construct a series from a Float32Array. This is the most efficient way to construct a
25
+ // series from a large amount of data.
26
26
  series = new Series(new Float32Array([1, 2, 3, 4, 5]));
27
27
 
28
- // Construct a series from a JSON object. This is useful when you have a series
29
- // that has been serialized to JSON.
30
- series = new Series([{ red: "cherry" }, { yellow: "banana" }, {orange: "orange" }]);
28
+ // Construct a series from a JSON object. This is useful when you have a series that has
29
+ // been serialized to JSON.
30
+ series = new Series([{ red: "cherry" }, { yellow: "banana" }, { orange: "orange" }]);
31
31
 
32
32
  series = new Series([1, "a", 3, "b", 5]);
33
33
 
@@ -60,16 +60,16 @@ series = new Series([{ red: "cherry", yellow: "banana", orange: "orange" }]);
60
60
  jsArray = [...series];
61
61
  console.log(jsArray); // [ { red: 'cherry', yellow: 'banana', orange: 'orange' } ]
62
62
 
63
- import { TimeRange, TimeStamp, TimeSpan } from "@synnaxlabs/client";
63
+ import { TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/client";
64
64
 
65
65
  const start = TimeStamp.now();
66
66
 
67
67
  const tr = new TimeRange(start, start.add(TimeSpan.seconds(5)));
68
68
 
69
69
  series = new Series({
70
- data: [1, 2, 3, 4, 5],
71
- dataType: "float64",
72
- timeRange: new TimeRange(start, start.add(TimeSpan.seconds(6)))
70
+ data: [1, 2, 3, 4, 5],
71
+ dataType: "float64",
72
+ timeRange: tr,
73
73
  });
74
74
 
75
75
  series = new Series([1, 2, 3, 4, 5]);
@@ -93,51 +93,52 @@ import { Frame } from "@synnaxlabs/client";
93
93
 
94
94
  // Construct a frame for the given channel names.
95
95
  let frame = new Frame({
96
- "channel1": new Series([1, 2, 3, 4, 5]),
97
- "channel2": new Series([5, 4, 3, 2, 1]),
98
- "channel3": new Series([1, 1, 1, 1, 1]),
96
+ channel1: new Series([1, 2, 3, 4, 5]),
97
+ channel2: new Series([5, 4, 3, 2, 1]),
98
+ channel3: new Series([1, 1, 1, 1, 1]),
99
99
  });
100
100
 
101
101
  // Construct a frame for the given channel keys
102
102
  frame = new Frame({
103
- 1: new Series([1, 2, 3, 4, 5]),
104
- 2: new Series([5, 4, 3, 2, 1]),
105
- // Notice that series do not need to be the same length.
106
- 3: new Series([1, 1, 1]),
103
+ 1: new Series([1, 2, 3, 4, 5]),
104
+ 2: new Series([5, 4, 3, 2, 1]),
105
+ // Notice that series do not need to be the same length.
106
+ 3: new Series([1, 1, 1]),
107
107
  });
108
108
 
109
109
  // Construct a frame from a map
110
- frame = new Frame(new Map([
111
- ["channel1", new Series([1, 2, 3, 4, 5])],
112
- ["channel2", new Series([5, 4, 3, 2, 1])],
113
- ["channel3", new Series([1, 1, 1, 1, 1])],
114
- ]));
110
+ frame = new Frame(
111
+ new Map([
112
+ ["channel1", new Series([1, 2, 3, 4, 5])],
113
+ ["channel2", new Series([5, 4, 3, 2, 1])],
114
+ ["channel3", new Series([1, 1, 1, 1, 1])],
115
+ ]),
116
+ );
115
117
 
116
118
  // Or from an array of keys and series
117
- frame = new Frame(["channel1", "channel2", "channel3"], [
118
- new Series([1, 2, 3, 4, 5]),
119
- new Series([5, 4, 3, 2, 1]),
120
- new Series([1, 1, 1, 1, 1]),
121
- ]);
119
+ frame = new Frame(
120
+ ["channel1", "channel2", "channel3"],
121
+ [
122
+ new Series([1, 2, 3, 4, 5]),
123
+ new Series([5, 4, 3, 2, 1]),
124
+ new Series([1, 1, 1, 1, 1]),
125
+ ],
126
+ );
122
127
 
123
128
  // Or construct a frame with multiple series for a single channel
124
129
  frame = new Frame({
125
- "channel1": [
126
- new Series([1, 2, 3, 4, 5]),
127
- new Series([5, 4, 3, 2, 1]),
128
- new Series([1, 1, 1, 1, 1]),
129
- ],
130
- "channel2": [
131
- new Series([1, 2, 3, 4, 5]),
132
- ],
130
+ channel1: [
131
+ new Series([1, 2, 3, 4, 5]),
132
+ new Series([5, 4, 3, 2, 1]),
133
+ new Series([1, 1, 1, 1, 1]),
134
+ ],
135
+ channel2: [new Series([1, 2, 3, 4, 5])],
133
136
  });
134
137
 
135
- import { MultiSeries } from "@synnaxlabs/client";
136
-
137
138
  frame = new Frame({
138
- "channel1": [new Series([1, 2]), new Series([3, 4, 5])],
139
- "channel2": new Series([5, 4, 3, 2, 1]),
140
- "channel3": new Series([1, 1, 1, 1, 1]),
139
+ channel1: [new Series([1, 2]), new Series([3, 4, 5])],
140
+ channel2: new Series([5, 4, 3, 2, 1]),
141
+ channel3: new Series([1, 1, 1, 1, 1]),
141
142
  });
142
143
 
143
144
  const multiSeries = frame.get("channel1");
@@ -152,21 +153,21 @@ jsArray = [...multiSeries];
152
153
  console.log(jsArray); // [ 1, 2, 3, 4, 5 ]
153
154
 
154
155
  frame = new Frame({
155
- "channel1": new Series([1, 2, 3, 4, 5]),
156
- "channel2": new Series([5, 4, 3, 2, 1]),
157
- "channel3": new Series([1, 1]),
156
+ channel1: new Series([1, 2, 3, 4, 5]),
157
+ channel2: new Series([5, 4, 3, 2, 1]),
158
+ channel3: new Series([1, 1]),
158
159
  });
159
160
 
160
161
  let obj = frame.at(3);
161
162
  console.log(obj); // { channel1: 1, channel2: 5, channel3: undefined }
162
163
 
163
164
  frame = new Frame({
164
- "channel1": new Series([1, 2, 3, 4, 5]),
165
- "channel2": new Series([5, 4, 3, 2, 1]),
166
- "channel3": new Series([1, 1]),
165
+ channel1: new Series([1, 2, 3, 4, 5]),
166
+ channel2: new Series([5, 4, 3, 2, 1]),
167
+ channel3: new Series([1, 1]),
167
168
  });
168
169
  try {
169
- obj = frame.at(3, true); // Throws an error
170
+ obj = frame.at(3, true); // Throws an error
170
171
  } catch (e) {
171
- console.log(e.message); // no value at index
172
+ console.log(e.message); // no value at index
172
173
  }
@@ -12,7 +12,7 @@
12
12
  // applications (such as data acquisition from a sensor) or for very large datasets that
13
13
  // cannot be written all at once.
14
14
 
15
- import { Rate, Series, Synnax, TimeStamp, framer } from "@synnaxlabs/client";
15
+ import { Rate, Synnax, TimeStamp } from "@synnaxlabs/client";
16
16
 
17
17
  // Connect to a locally running, insecure Synnax cluster. If your connection parameters
18
18
  // are different, enter them here.
@@ -24,51 +24,58 @@ const client = new Synnax({
24
24
  });
25
25
 
26
26
  // Create an index channel that will be used to store our timestamps.
27
- const timeChannel = await client.channels.create({
28
- name: "stream_write_example_time",
29
- isIndex: true,
30
- dataType: "timestamp"
31
- }, { retrieveIfNameExists: true });
27
+ const timeChannel = await client.channels.create(
28
+ {
29
+ name: "stream_write_example_time",
30
+ isIndex: true,
31
+ dataType: "timestamp",
32
+ },
33
+ { retrieveIfNameExists: true },
34
+ );
32
35
 
33
36
  // Create a data channel that will be used to store our fake sensor data.
34
- const dataChannel1 = await client.channels.create({
35
- name: "stream_write_example_data_1",
36
- dataType: "float32",
37
- index: timeChannel.key,
38
- }, { retrieveIfNameExists: true });
39
-
40
- const dataChannel2 = await client.channels.create({
41
- name: "stream_write_example_data_2",
42
- dataType: "int32",
43
- index: timeChannel.key,
44
- }, { retrieveIfNameExists: true });
37
+ const dataChannel1 = await client.channels.create(
38
+ {
39
+ name: "stream_write_example_data_1",
40
+ dataType: "float32",
41
+ index: timeChannel.key,
42
+ },
43
+ { retrieveIfNameExists: true },
44
+ );
45
45
 
46
+ const dataChannel2 = await client.channels.create(
47
+ {
48
+ name: "stream_write_example_data_2",
49
+ dataType: "int32",
50
+ index: timeChannel.key,
51
+ },
52
+ { retrieveIfNameExists: true },
53
+ );
46
54
 
47
55
  // We'll start our write at the current time. This timestamps should be the same as or
48
56
  // just before the first timestamp we write.
49
57
  const start = TimeStamp.now();
50
58
 
51
- // Set a rough rate of 20 Hz. This won't be exact because we're sleeping for a fixed
59
+ // Set a rough rate of 25 Hz. This won't be exact because we're sleeping for a fixed
52
60
  // amount of time, but it's close enough for demonstration purposes.
53
61
  const roughRate = Rate.hz(25);
54
62
 
55
- // Make the writer commit every 500 samples. This will make the data available for
56
- // historical reads every 500 samples.
57
- const commitInterval = 500;
58
-
59
63
  const writer = await client.openWriter({
60
64
  start,
61
65
  channels: [timeChannel.key, dataChannel1.key, dataChannel2.key],
66
+ enableAutoCommit: true,
62
67
  });
63
68
 
64
69
  try {
65
70
  let i = 0;
66
71
  while (true) {
67
- await new Promise(resolve => setTimeout(resolve, roughRate.period.milliseconds));
72
+ await new Promise((resolve) =>
73
+ setTimeout(resolve, roughRate.period.milliseconds),
74
+ );
68
75
  i++;
69
76
  const timestamp = TimeStamp.now();
70
- const data2= i % 2;
71
77
  const data1 = Math.sin(i / 10);
78
+ const data2 = i % 2;
72
79
  await writer.write({
73
80
  [timeChannel.key]: timestamp,
74
81
  [dataChannel1.key]: data1,
@@ -76,16 +83,10 @@ try {
76
83
  });
77
84
 
78
85
  if (i % 60 == 0)
79
- console.log(`Writing sample ${i} at ${timestamp.toISOString()}`)
80
-
81
- // Commit the writer. This method will return false if the commit fails i.e.
82
- // we've mad an invalid write or someone has already written to this region.
83
- if (i % commitInterval == 0 && !await writer.commit()) {
84
- console.error("Failed to commit data");
85
- break;
86
- }
86
+ console.log(`Writing sample ${i} at ${timestamp.toISOString()}`);
87
87
  }
88
88
  } finally {
89
+ // Close the writer and the client when you are done
89
90
  await writer.close();
91
+ client.close();
90
92
  }
91
-
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@synnaxlabs/client",
3
3
  "private": false,
4
- "version": "0.25.0",
4
+ "version": "0.26.1",
5
5
  "type": "module",
6
6
  "description": "The Client Library for Synnax",
7
7
  "repository": "https://github.com/synnaxlabs/synnax/tree/main/client/ts",
@@ -18,8 +18,8 @@
18
18
  "dependencies": {
19
19
  "async-mutex": "^0.5.0",
20
20
  "zod": "3.23.8",
21
- "@synnaxlabs/freighter": "0.25.0",
22
- "@synnaxlabs/x": "0.25.0"
21
+ "@synnaxlabs/freighter": "0.26.0",
22
+ "@synnaxlabs/x": "0.26.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^20.14.9",
@@ -28,9 +28,9 @@
28
28
  "typescript": "^5.5.3",
29
29
  "vite": "5.3.3",
30
30
  "vitest": "^1.6.0",
31
- "@synnaxlabs/tsconfig": "0.0.2",
32
31
  "@synnaxlabs/vite-plugin": "0.0.1",
33
- "eslint-config-synnaxlabs": "0.0.1"
32
+ "eslint-config-synnaxlabs": "0.0.1",
33
+ "@synnaxlabs/tsconfig": "0.0.2"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "zod": "^3.23.8"
@@ -0,0 +1,276 @@
1
+ // Copyright 2024 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 { DataType, id, Rate } from "@synnaxlabs/x";
11
+ import { describe, expect, test } from "vitest";
12
+
13
+ import { Policy } from "@/access/payload";
14
+ import { Channel } from "@/channel/client";
15
+ import { ChannelOntologyType } from "@/channel/payload";
16
+ import Synnax from "@/client";
17
+ import { AuthError } from "@/errors";
18
+ import { LabelOntologyType } from "@/label/payload";
19
+ import { HOST, newClient, PORT } from "@/setupspecs";
20
+ import { UserOntologyType } from "@/user/payload";
21
+
22
+ const client = newClient();
23
+
24
+ describe("Policy", () => {
25
+ describe("create", () => {
26
+ test("create one", async () => {
27
+ const policy = await client.access.create({
28
+ subjects: [{ type: UserOntologyType, key: "1" }],
29
+ objects: [
30
+ { type: UserOntologyType, key: "2" },
31
+ { type: ChannelOntologyType, key: "3" },
32
+ ],
33
+ actions: ["update"],
34
+ });
35
+ expect(policy.key).not.toEqual("");
36
+ expect(policy.subjects).toEqual([{ type: UserOntologyType, key: "1" }]);
37
+ expect(policy.objects).toEqual([
38
+ { type: UserOntologyType, key: "2" },
39
+ { type: ChannelOntologyType, key: "3" },
40
+ ]);
41
+ expect(policy.actions).toEqual(["update"]);
42
+ });
43
+ test("create many", async () => {
44
+ const policies = await client.access.create([
45
+ {
46
+ subjects: [{ type: UserOntologyType, key: "10" }],
47
+ objects: [
48
+ { type: UserOntologyType, key: "20" },
49
+ { type: UserOntologyType, key: "21" },
50
+ ],
51
+ actions: ["update"],
52
+ },
53
+ {
54
+ subjects: [
55
+ { type: UserOntologyType, key: "20" },
56
+ { type: UserOntologyType, key: "21" },
57
+ ],
58
+ objects: [
59
+ { type: UserOntologyType, key: "20" },
60
+ { type: ChannelOntologyType, key: "30" },
61
+ ],
62
+ actions: ["update"],
63
+ },
64
+ ]);
65
+ expect(policies.length).toEqual(2);
66
+ expect(policies[0].subjects[0].key).toEqual("10");
67
+ expect(policies[1].subjects[1].key).toEqual("21");
68
+ });
69
+ test("create instances of policies", async () => {
70
+ const policy = {
71
+ key: undefined,
72
+ subjects: [
73
+ { type: UserOntologyType, key: "20" },
74
+ { type: UserOntologyType, key: "21" },
75
+ ],
76
+ objects: [
77
+ { type: UserOntologyType, key: "20" },
78
+ { type: ChannelOntologyType, key: "30" },
79
+ ],
80
+ actions: ["update"],
81
+ };
82
+
83
+ const p = await client.access.create(policy);
84
+ expect(p.subjects).toEqual(policy.subjects);
85
+ expect(p.key).not.toEqual(policy.key);
86
+ });
87
+ });
88
+ test("retrieve by subject", async () => {
89
+ const key1 = id.id();
90
+ const policies = [
91
+ {
92
+ key: undefined,
93
+ subjects: [
94
+ { type: UserOntologyType, key: key1 },
95
+ { type: UserOntologyType, key: "21" },
96
+ ],
97
+ objects: [
98
+ { type: UserOntologyType, key: "234" },
99
+ { type: ChannelOntologyType, key: "30" },
100
+ ],
101
+ actions: ["update"],
102
+ },
103
+ {
104
+ key: undefined,
105
+ subjects: [
106
+ { type: UserOntologyType, key: key1 },
107
+ { type: UserOntologyType, key: "22" },
108
+ ],
109
+ objects: [
110
+ { type: LabelOntologyType, key: "23123" },
111
+ { type: ChannelOntologyType, key: "30" },
112
+ ],
113
+ actions: ["delete"],
114
+ },
115
+ ];
116
+
117
+ await client.access.create(policies);
118
+
119
+ const p = (await client.access.retrieve(policies[0].subjects[0])) as Policy[];
120
+ expect(p).toHaveLength(2);
121
+ expect([p[0].actions, p[1].actions].sort()).toEqual([["delete"], ["update"]]);
122
+ });
123
+ test("retrieve by subject - not found", async () => {
124
+ const res = await client.access.retrieve({ type: UserOntologyType, key: "999" });
125
+ expect(res).toHaveLength(0);
126
+ });
127
+
128
+ describe("delete", async () => {
129
+ test("delete one", async () => {
130
+ const id1 = id.id();
131
+ const id2 = id.id();
132
+ const id3 = id.id();
133
+ const policies = [
134
+ {
135
+ key: undefined,
136
+ subjects: [
137
+ { type: UserOntologyType, key: id1 },
138
+ { type: UserOntologyType, key: id2 },
139
+ ],
140
+ objects: [
141
+ { type: UserOntologyType, key: "20" },
142
+ { type: ChannelOntologyType, key: "30" },
143
+ ],
144
+ actions: ["update"],
145
+ },
146
+ {
147
+ key: undefined,
148
+ subjects: [
149
+ { type: UserOntologyType, key: id1 },
150
+ { type: UserOntologyType, key: id3 },
151
+ ],
152
+ objects: [
153
+ { type: LabelOntologyType, key: "20" },
154
+ { type: ChannelOntologyType, key: "30" },
155
+ ],
156
+ actions: ["delete"],
157
+ },
158
+ ];
159
+
160
+ const created = await client.access.create(policies);
161
+ await client.access.delete(created[0].key);
162
+ const res = await client.access.retrieve(created[0].subjects[0]);
163
+ expect(res).toHaveLength(1);
164
+ expect(res[0].actions).toEqual(["delete"]);
165
+ });
166
+ test("delete many", async () => {
167
+ const id1 = id.id();
168
+ const id2 = id.id();
169
+ const id3 = id.id();
170
+ const policies = [
171
+ {
172
+ subjects: [
173
+ { type: UserOntologyType, key: id1 },
174
+ { type: UserOntologyType, key: id2 },
175
+ ],
176
+ objects: [
177
+ { type: UserOntologyType, key: "20" },
178
+ { type: ChannelOntologyType, key: "30" },
179
+ ],
180
+ actions: ["update"],
181
+ },
182
+ {
183
+ subjects: [
184
+ { type: UserOntologyType, key: id1 },
185
+ { type: UserOntologyType, key: id3 },
186
+ ],
187
+ objects: [
188
+ { type: LabelOntologyType, key: "20" },
189
+ { type: ChannelOntologyType, key: "30" },
190
+ ],
191
+ actions: ["delete"],
192
+ },
193
+ ];
194
+
195
+ const created = await client.access.create(policies);
196
+ await client.access.delete([created[0].key, created[1].key]);
197
+ let res = await client.access.retrieve({ type: UserOntologyType, key: id1 });
198
+ expect(res).toHaveLength(0);
199
+ res = await client.access.retrieve({ type: UserOntologyType, key: id2 });
200
+ expect(res).toHaveLength(0);
201
+ res = await client.access.retrieve({ type: UserOntologyType, key: id3 });
202
+ expect(res).toHaveLength(0);
203
+ });
204
+ });
205
+ describe("registration", async () => {
206
+ test("register a user", async () => {
207
+ const username = id.id();
208
+ await client.user.register(username, "pwd1");
209
+ new Synnax({
210
+ host: HOST,
211
+ port: PORT,
212
+ username: username,
213
+ password: "pwd1",
214
+ });
215
+ });
216
+ test("duplicate username", async () => {
217
+ const username = id.id();
218
+ await client.user.register(username, "pwd1");
219
+ await expect(client.user.register(username, "pwd1")).rejects.toThrow(AuthError);
220
+ });
221
+ });
222
+ describe("privilege", async () => {
223
+ test("new user", async () => {
224
+ const username = id.id();
225
+ const user2 = await client.user.register(username, "pwd1");
226
+ expect(user2).toBeDefined();
227
+ const client2 = new Synnax({
228
+ host: HOST,
229
+ port: PORT,
230
+ username: username,
231
+ password: "pwd1",
232
+ });
233
+
234
+ await expect(
235
+ client2.user.register(id.id(), "pwd3")
236
+ ).rejects.toThrow(AuthError);
237
+
238
+ await expect(
239
+ client2.channels.create(
240
+ new Channel({
241
+ dataType: DataType.FLOAT64,
242
+ rate: Rate.hz(1),
243
+ name: "my_channel",
244
+ }),
245
+ ),
246
+ ).rejects.toThrow(AuthError);
247
+
248
+ const p = await client.access.create({
249
+ subjects: [{ type: UserOntologyType, key: user2.key }],
250
+ objects: [{ type: ChannelOntologyType, key: "" }],
251
+ actions: ["create"],
252
+ });
253
+
254
+ await client2.channels.create(
255
+ new Channel({
256
+ dataType: DataType.FLOAT64,
257
+ rate: Rate.hz(1),
258
+ name: "my_channel",
259
+ }),
260
+ );
261
+
262
+ // Remove privileges
263
+ await client.access.delete(p.key);
264
+
265
+ await expect(
266
+ client2.channels.create(
267
+ new Channel({
268
+ dataType: DataType.FLOAT64,
269
+ rate: Rate.hz(1),
270
+ name: "my_channel",
271
+ }),
272
+ ),
273
+ ).rejects.toThrow(AuthError);
274
+ });
275
+ });
276
+ });
@@ -0,0 +1,86 @@
1
+ // Copyright 2024 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 { toArray } from "@synnaxlabs/x/toArray";
12
+ import { z } from "zod";
13
+
14
+ import {
15
+ Key,
16
+ keyZ,
17
+ NewPolicyPayload,
18
+ newPolicyPayloadZ,
19
+ Policy,
20
+ policyZ,
21
+ } from "@/access/payload";
22
+ import { IDPayload, idZ } from "@/ontology/payload";
23
+
24
+ const CREATE_ENDPOINT = "/access/policy/create";
25
+ const DELETE_ENDPOINT = "/access/policy/delete";
26
+ const RETRIEVE_ENDPOINT = "/access/policy/retrieve";
27
+
28
+ const createReqZ = z.object({ policies: newPolicyPayloadZ.array() });
29
+ const createResZ = z.object({ policies: policyZ.array() });
30
+
31
+ const deleteReqZ = z.object({ keys: keyZ.array() });
32
+ const deleteResZ = z.object({});
33
+
34
+ const retrieveReqZ = z.object({ subject: idZ });
35
+ const retrieveResZ = z.object({
36
+ policies: policyZ.array().optional().default([]),
37
+ });
38
+
39
+ export class Client {
40
+ private readonly client: UnaryClient;
41
+
42
+ constructor(client: UnaryClient) {
43
+ this.client = client;
44
+ }
45
+
46
+ async create(policies: NewPolicyPayload): Promise<Policy>;
47
+
48
+ async create(policies: NewPolicyPayload[]): Promise<Policy[]>;
49
+
50
+ async create(
51
+ policies: NewPolicyPayload | NewPolicyPayload[],
52
+ ): Promise<Policy | Policy[]> {
53
+ const single = !Array.isArray(policies);
54
+
55
+ const { policies: created } = await sendRequired<
56
+ typeof createReqZ,
57
+ typeof createResZ
58
+ >(
59
+ this.client,
60
+ CREATE_ENDPOINT,
61
+ { policies: toArray(policies) },
62
+ createReqZ,
63
+ createResZ,
64
+ );
65
+
66
+ return single ? created[0] : created;
67
+ }
68
+
69
+ async retrieve(subject: IDPayload): Promise<Policy[]> {
70
+ const { policies: retrieved } = await sendRequired<
71
+ typeof retrieveReqZ,
72
+ typeof retrieveResZ
73
+ >(this.client, RETRIEVE_ENDPOINT, { subject: subject }, retrieveReqZ, retrieveResZ);
74
+ return retrieved;
75
+ }
76
+
77
+ async delete(keys: Key | Key[]): Promise<void> {
78
+ await sendRequired<typeof deleteReqZ, typeof deleteResZ>(
79
+ this.client,
80
+ DELETE_ENDPOINT,
81
+ { keys: toArray(keys) },
82
+ deleteReqZ,
83
+ deleteResZ,
84
+ );
85
+ }
86
+ }
@@ -0,0 +1,11 @@
1
+ // Copyright 2024 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 "@/access/client";
11
+ export * from "@/access/payload";
@@ -0,0 +1,10 @@
1
+ // Copyright 2024 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 access from "@/access/external";