@synnaxlabs/client 0.41.0 → 0.42.0
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 +6 -6
- package/dist/access/payload.d.ts +7 -1
- package/dist/access/payload.d.ts.map +1 -1
- package/dist/access/policy/payload.d.ts +182 -142
- package/dist/access/policy/payload.d.ts.map +1 -1
- package/dist/access/policy/retriever.d.ts +25 -22
- package/dist/access/policy/retriever.d.ts.map +1 -1
- package/dist/auth/auth.d.ts +1 -7
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/channel/client.d.ts +2 -7
- package/dist/channel/client.d.ts.map +1 -1
- package/dist/channel/payload.d.ts +13 -74
- package/dist/channel/payload.d.ts.map +1 -1
- package/dist/channel/retriever.d.ts +5 -31
- package/dist/channel/retriever.d.ts.map +1 -1
- package/dist/channel/writer.d.ts +6 -18
- package/dist/channel/writer.d.ts.map +1 -1
- package/dist/client.cjs +36 -31
- package/dist/client.d.ts +8 -56
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +6486 -3979
- package/dist/connection/checker.d.ts +22 -39
- package/dist/connection/checker.d.ts.map +1 -1
- package/dist/control/client.d.ts.map +1 -1
- package/dist/control/state.d.ts +6 -26
- package/dist/control/state.d.ts.map +1 -1
- package/dist/errors.d.ts +31 -56
- package/dist/errors.d.ts.map +1 -1
- package/dist/framer/adapter.d.ts +4 -0
- package/dist/framer/adapter.d.ts.map +1 -1
- package/dist/framer/client.d.ts +2 -2
- package/dist/framer/client.d.ts.map +1 -1
- package/dist/framer/codec.d.ts +34 -0
- package/dist/framer/codec.d.ts.map +1 -0
- package/dist/framer/codec.spec.d.ts +2 -0
- package/dist/framer/codec.spec.d.ts.map +1 -0
- package/dist/framer/deleter.d.ts +12 -49
- package/dist/framer/deleter.d.ts.map +1 -1
- package/dist/framer/frame.d.ts +26 -88
- package/dist/framer/frame.d.ts.map +1 -1
- package/dist/framer/iterator.d.ts.map +1 -1
- package/dist/framer/streamer.d.ts +69 -11
- package/dist/framer/streamer.d.ts.map +1 -1
- package/dist/framer/writer.d.ts +60 -257
- package/dist/framer/writer.d.ts.map +1 -1
- package/dist/hardware/device/client.d.ts +7 -31
- package/dist/hardware/device/client.d.ts.map +1 -1
- package/dist/hardware/device/payload.d.ts +11 -93
- package/dist/hardware/device/payload.d.ts.map +1 -1
- package/dist/hardware/rack/payload.d.ts +15 -103
- package/dist/hardware/rack/payload.d.ts.map +1 -1
- package/dist/hardware/task/client.d.ts +4 -20
- package/dist/hardware/task/client.d.ts.map +1 -1
- package/dist/hardware/task/payload.d.ts +41 -116
- package/dist/hardware/task/payload.d.ts.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/label/payload.d.ts +1 -9
- package/dist/label/payload.d.ts.map +1 -1
- package/dist/label/writer.d.ts +27 -36
- package/dist/label/writer.d.ts.map +1 -1
- package/dist/ontology/client.d.ts +46 -36
- package/dist/ontology/client.d.ts.map +1 -1
- package/dist/ontology/group/payload.d.ts +1 -7
- package/dist/ontology/group/payload.d.ts.map +1 -1
- package/dist/ontology/payload.d.ts +239 -146
- package/dist/ontology/payload.d.ts.map +1 -1
- package/dist/ranger/client.d.ts +13 -58
- package/dist/ranger/client.d.ts.map +1 -1
- package/dist/ranger/kv.d.ts +7 -49
- package/dist/ranger/kv.d.ts.map +1 -1
- package/dist/ranger/payload.d.ts +21 -99
- package/dist/ranger/payload.d.ts.map +1 -1
- package/dist/ranger/writer.d.ts +35 -88
- package/dist/ranger/writer.d.ts.map +1 -1
- package/dist/testutil/indexedPair.d.ts +5 -0
- package/dist/testutil/indexedPair.d.ts.map +1 -0
- package/dist/testutil/telem.d.ts +3 -0
- package/dist/testutil/telem.d.ts.map +1 -0
- package/dist/transport.d.ts +2 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/user/payload.d.ts +3 -29
- package/dist/user/payload.d.ts.map +1 -1
- package/dist/user/retriever.d.ts +3 -9
- package/dist/user/retriever.d.ts.map +1 -1
- package/dist/util/decodeJSONString.d.ts.map +1 -1
- package/dist/util/zod.d.ts +1 -1
- package/dist/util/zod.d.ts.map +1 -1
- package/dist/workspace/lineplot/payload.d.ts +10 -26
- package/dist/workspace/lineplot/payload.d.ts.map +1 -1
- package/dist/workspace/log/payload.d.ts +10 -26
- package/dist/workspace/log/payload.d.ts.map +1 -1
- package/dist/workspace/payload.d.ts +14 -40
- package/dist/workspace/payload.d.ts.map +1 -1
- package/dist/workspace/schematic/payload.d.ts +13 -45
- package/dist/workspace/schematic/payload.d.ts.map +1 -1
- package/dist/workspace/table/payload.d.ts +13 -39
- package/dist/workspace/table/payload.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/channel/channel.spec.ts +26 -27
- package/src/channel/client.ts +0 -9
- package/src/channel/payload.ts +22 -5
- package/src/channel/retriever.ts +12 -6
- package/src/client.ts +3 -3
- package/src/control/client.ts +5 -2
- package/src/control/state.ts +8 -3
- package/src/errors.spec.ts +5 -4
- package/src/errors.ts +21 -82
- package/src/framer/adapter.ts +22 -3
- package/src/framer/client.ts +38 -21
- package/src/framer/codec.spec.ts +303 -0
- package/src/framer/codec.ts +396 -0
- package/src/framer/deleter.spec.ts +51 -63
- package/src/framer/frame.ts +16 -5
- package/src/framer/iterator.spec.ts +45 -28
- package/src/framer/iterator.ts +6 -5
- package/src/framer/streamProxy.ts +1 -1
- package/src/framer/streamer.spec.ts +10 -18
- package/src/framer/streamer.ts +138 -22
- package/src/framer/writer.spec.ts +125 -150
- package/src/framer/writer.ts +74 -68
- package/src/hardware/device/payload.ts +3 -3
- package/src/hardware/task/payload.ts +9 -6
- package/src/hardware/task/task.spec.ts +1 -1
- package/src/index.ts +0 -2
- package/src/ontology/group/group.spec.ts +2 -2
- package/src/ontology/payload.ts +3 -3
- package/src/ranger/ranger.spec.ts +9 -7
- package/src/testutil/indexedPair.ts +40 -0
- package/src/testutil/telem.ts +13 -0
- package/src/transport.ts +1 -2
- package/src/util/decodeJSONString.ts +2 -2
- package/src/util/retrieve.spec.ts +1 -1
|
@@ -7,33 +7,29 @@
|
|
|
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 {
|
|
11
|
-
import {
|
|
12
|
-
import { describe, expect, test } from "vitest";
|
|
10
|
+
import { DataType, TimeRange, TimeSpan, TimeStamp } from "@synnaxlabs/x/telem";
|
|
11
|
+
import { describe, expect, it, test } from "vitest";
|
|
13
12
|
|
|
14
|
-
import { type channel } from "@/channel";
|
|
15
13
|
import { UnauthorizedError, ValidationError } from "@/errors";
|
|
16
14
|
import { ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT, WriterMode } from "@/framer/writer";
|
|
17
15
|
import { newClient } from "@/setupspecs";
|
|
16
|
+
import { newIndexedPair } from "@/testutil/indexedPair";
|
|
17
|
+
import { secondsLinspace } from "@/testutil/telem";
|
|
18
18
|
import { randomSeries } from "@/util/telem";
|
|
19
19
|
|
|
20
20
|
const client = newClient();
|
|
21
|
-
|
|
22
|
-
const newChannel = async (): Promise<channel.Channel> =>
|
|
23
|
-
await client.channels.create({
|
|
24
|
-
leaseholder: 1,
|
|
25
|
-
name: `test-${id.create()}`,
|
|
26
|
-
rate: Rate.hz(1),
|
|
27
|
-
dataType: DataType.FLOAT64,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
21
|
describe("Writer", () => {
|
|
31
22
|
describe("Writer", () => {
|
|
32
23
|
test("basic write", async () => {
|
|
33
|
-
const
|
|
34
|
-
const
|
|
24
|
+
const channels = await newIndexedPair(client);
|
|
25
|
+
const start = TimeStamp.seconds(1);
|
|
26
|
+
const writer = await client.openWriter({ start, channels });
|
|
27
|
+
const [index, data] = channels;
|
|
35
28
|
try {
|
|
36
|
-
await writer.write(
|
|
29
|
+
await writer.write({
|
|
30
|
+
[index.key]: secondsLinspace(1, 10),
|
|
31
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
32
|
+
});
|
|
37
33
|
await writer.commit();
|
|
38
34
|
} finally {
|
|
39
35
|
await writer.close();
|
|
@@ -42,24 +38,28 @@ describe("Writer", () => {
|
|
|
42
38
|
});
|
|
43
39
|
|
|
44
40
|
test("write to unknown channel key", async () => {
|
|
45
|
-
const
|
|
46
|
-
const writer = await client.openWriter({ start:
|
|
41
|
+
const channels = await newIndexedPair(client);
|
|
42
|
+
const writer = await client.openWriter({ start: TimeStamp.now(), channels });
|
|
47
43
|
await expect(
|
|
48
44
|
writer.write("billy bob", randomSeries(10, DataType.FLOAT64)),
|
|
49
|
-
).rejects.toThrow(
|
|
45
|
+
).rejects.toThrow('Channel "billy bob" not found');
|
|
50
46
|
await writer.close();
|
|
51
47
|
});
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const stream = await client.openStreamer(
|
|
49
|
+
it("should not stream data when mode is set ot persist only", async () => {
|
|
50
|
+
const channels = await newIndexedPair(client);
|
|
51
|
+
const stream = await client.openStreamer(channels);
|
|
56
52
|
const writer = await client.openWriter({
|
|
57
|
-
start:
|
|
58
|
-
channels
|
|
53
|
+
start: TimeStamp.seconds(1),
|
|
54
|
+
channels,
|
|
59
55
|
mode: WriterMode.Persist,
|
|
60
56
|
});
|
|
57
|
+
const [index, data] = channels;
|
|
61
58
|
try {
|
|
62
|
-
await writer.write(
|
|
59
|
+
await writer.write({
|
|
60
|
+
[index.key]: secondsLinspace(1, 10),
|
|
61
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
62
|
+
});
|
|
63
63
|
} finally {
|
|
64
64
|
await writer.close();
|
|
65
65
|
}
|
|
@@ -72,33 +72,44 @@ describe("Writer", () => {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
test("write with auto commit on", async () => {
|
|
75
|
-
const
|
|
75
|
+
const channels = await newIndexedPair(client);
|
|
76
76
|
const writer = await client.openWriter({
|
|
77
|
-
start:
|
|
78
|
-
channels
|
|
77
|
+
start: TimeStamp.seconds(1),
|
|
78
|
+
channels,
|
|
79
79
|
enableAutoCommit: true,
|
|
80
80
|
});
|
|
81
|
+
const [index, data] = channels;
|
|
81
82
|
try {
|
|
82
|
-
await writer.write(
|
|
83
|
+
await writer.write({
|
|
84
|
+
[index.key]: secondsLinspace(1, 10),
|
|
85
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
86
|
+
});
|
|
83
87
|
} finally {
|
|
84
88
|
await writer.close();
|
|
85
89
|
}
|
|
86
90
|
expect(true).toBeTruthy();
|
|
87
91
|
|
|
88
|
-
const f = await client.read(
|
|
92
|
+
const f = await client.read(
|
|
93
|
+
new TimeRange(TimeStamp.seconds(1), TimeStamp.seconds(11)),
|
|
94
|
+
index.key,
|
|
95
|
+
);
|
|
89
96
|
expect(f.length).toEqual(10);
|
|
90
97
|
});
|
|
91
98
|
|
|
92
99
|
test("write with auto commit and alwaysPersist", async () => {
|
|
93
|
-
const
|
|
100
|
+
const channels = await newIndexedPair(client);
|
|
94
101
|
const writer = await client.openWriter({
|
|
95
|
-
start:
|
|
96
|
-
channels
|
|
102
|
+
start: TimeStamp.seconds(1),
|
|
103
|
+
channels,
|
|
97
104
|
enableAutoCommit: true,
|
|
98
105
|
autoIndexPersistInterval: ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT,
|
|
99
106
|
});
|
|
107
|
+
const [index, data] = channels;
|
|
100
108
|
try {
|
|
101
|
-
await writer.write(
|
|
109
|
+
await writer.write({
|
|
110
|
+
[index.key]: secondsLinspace(1, 10),
|
|
111
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
112
|
+
});
|
|
102
113
|
} finally {
|
|
103
114
|
await writer.close();
|
|
104
115
|
}
|
|
@@ -106,21 +117,41 @@ describe("Writer", () => {
|
|
|
106
117
|
});
|
|
107
118
|
|
|
108
119
|
test("write with auto commit and a set interval", async () => {
|
|
109
|
-
const
|
|
120
|
+
const channels = await newIndexedPair(client);
|
|
110
121
|
const writer = await client.openWriter({
|
|
111
|
-
start:
|
|
112
|
-
channels
|
|
122
|
+
start: TimeStamp.seconds(1),
|
|
123
|
+
channels,
|
|
113
124
|
enableAutoCommit: true,
|
|
114
125
|
autoIndexPersistInterval: TimeSpan.milliseconds(100),
|
|
115
126
|
});
|
|
127
|
+
const [index, data] = channels;
|
|
116
128
|
try {
|
|
117
|
-
await writer.write(
|
|
129
|
+
await writer.write({
|
|
130
|
+
[index.key]: secondsLinspace(1, 10),
|
|
131
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
132
|
+
});
|
|
118
133
|
} finally {
|
|
119
134
|
await writer.close();
|
|
120
135
|
}
|
|
121
136
|
expect(true).toBeTruthy();
|
|
122
137
|
});
|
|
123
138
|
|
|
139
|
+
test("write with auto-commit off and incorrect data length validation error", async () => {
|
|
140
|
+
const channels = await newIndexedPair(client);
|
|
141
|
+
const writer = await client.openWriter({
|
|
142
|
+
start: TimeStamp.seconds(1),
|
|
143
|
+
channels,
|
|
144
|
+
});
|
|
145
|
+
await expect(async () => {
|
|
146
|
+
await writer.write({
|
|
147
|
+
[channels[0].key]: secondsLinspace(1, 10),
|
|
148
|
+
[channels[1].key]: randomSeries(11, channels[1].dataType),
|
|
149
|
+
});
|
|
150
|
+
await writer.commit();
|
|
151
|
+
await writer.close();
|
|
152
|
+
}).rejects.toThrow(ValidationError);
|
|
153
|
+
});
|
|
154
|
+
|
|
124
155
|
test("write with out of order timestamp", async () => {
|
|
125
156
|
const indexCh = await client.channels.create({
|
|
126
157
|
name: "idx",
|
|
@@ -140,159 +171,103 @@ describe("Writer", () => {
|
|
|
140
171
|
enableAutoCommit: true,
|
|
141
172
|
});
|
|
142
173
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
174
|
+
await expect(async () => {
|
|
175
|
+
for (let i = 0; i < 10; i++) {
|
|
176
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
177
|
+
await writer.write({
|
|
178
|
+
[indexCh.key]: new TimeStamp(i),
|
|
179
|
+
[dataCh.key]: i,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}).rejects.toThrow(ValidationError);
|
|
183
|
+
await expect(async () => {
|
|
184
|
+
await writer.close();
|
|
185
|
+
}).rejects.toThrow(ValidationError);
|
|
186
|
+
}, 5000000);
|
|
157
187
|
|
|
158
188
|
test("write with errOnUnauthorized", async () => {
|
|
159
|
-
const
|
|
189
|
+
const channels = await newIndexedPair(client);
|
|
160
190
|
const w1 = await client.openWriter({
|
|
161
191
|
start: new TimeStamp(TimeSpan.milliseconds(500)),
|
|
162
|
-
channels
|
|
192
|
+
channels,
|
|
163
193
|
});
|
|
164
194
|
|
|
165
195
|
await expect(
|
|
166
|
-
client.openWriter({
|
|
196
|
+
client.openWriter({
|
|
197
|
+
start: TimeStamp.now(),
|
|
198
|
+
channels,
|
|
199
|
+
errOnUnauthorized: true,
|
|
200
|
+
}),
|
|
167
201
|
).rejects.toThrow(UnauthorizedError);
|
|
168
202
|
await w1.close();
|
|
169
203
|
});
|
|
170
204
|
|
|
171
205
|
test("setAuthority", async () => {
|
|
172
|
-
const
|
|
206
|
+
const channels = await newIndexedPair(client);
|
|
207
|
+
const start = TimeStamp.seconds(5);
|
|
173
208
|
const w1 = await client.openWriter({
|
|
174
|
-
start
|
|
175
|
-
channels
|
|
209
|
+
start,
|
|
210
|
+
channels,
|
|
176
211
|
authorities: 10,
|
|
177
212
|
enableAutoCommit: true,
|
|
178
213
|
});
|
|
179
214
|
const w2 = await client.openWriter({
|
|
180
|
-
start
|
|
181
|
-
channels
|
|
215
|
+
start,
|
|
216
|
+
channels,
|
|
182
217
|
authorities: 20,
|
|
183
218
|
enableAutoCommit: true,
|
|
184
219
|
});
|
|
185
|
-
|
|
186
|
-
await w1.write(
|
|
187
|
-
|
|
220
|
+
const [index, data] = channels;
|
|
221
|
+
await w1.write({
|
|
222
|
+
[index.key]: secondsLinspace(5, 10),
|
|
223
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
224
|
+
});
|
|
225
|
+
let f = await index.read(TimeRange.MAX);
|
|
188
226
|
expect(f.length).toEqual(0);
|
|
189
227
|
|
|
190
|
-
await w1.setAuthority(
|
|
191
|
-
await w1.write(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
228
|
+
await w1.setAuthority(100);
|
|
229
|
+
await w1.write({
|
|
230
|
+
[index.key]: secondsLinspace(5, 10),
|
|
231
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
232
|
+
});
|
|
196
233
|
await w1.close();
|
|
197
234
|
await w2.close();
|
|
235
|
+
f = await index.read(TimeRange.MAX);
|
|
236
|
+
expect(f.length).toEqual(10);
|
|
198
237
|
});
|
|
199
238
|
|
|
200
239
|
test("setAuthority with name keys", async () => {
|
|
201
|
-
const
|
|
240
|
+
const channels = await newIndexedPair(client);
|
|
241
|
+
const start = TimeStamp.seconds(5);
|
|
202
242
|
const w1 = await client.openWriter({
|
|
203
|
-
start
|
|
204
|
-
channels
|
|
243
|
+
start,
|
|
244
|
+
channels,
|
|
205
245
|
authorities: 10,
|
|
206
246
|
enableAutoCommit: true,
|
|
207
247
|
});
|
|
208
248
|
const w2 = await client.openWriter({
|
|
209
|
-
start
|
|
210
|
-
channels
|
|
249
|
+
start,
|
|
250
|
+
channels,
|
|
211
251
|
authorities: 20,
|
|
212
252
|
enableAutoCommit: true,
|
|
213
253
|
});
|
|
214
|
-
|
|
215
|
-
await w1.write(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
await w1.setAuthority({ [ch.name]: 100 });
|
|
220
|
-
await w1.write(ch.key, randomSeries(10, ch.dataType));
|
|
221
|
-
|
|
222
|
-
f = await ch.read(TimeRange.MAX);
|
|
223
|
-
expect(f.length).toEqual(10);
|
|
224
|
-
|
|
225
|
-
await w1.close();
|
|
226
|
-
await w2.close();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test("setAuthority with name-value pair", async () => {
|
|
230
|
-
const ch = await newChannel();
|
|
231
|
-
const w1 = await client.openWriter({
|
|
232
|
-
start: 0,
|
|
233
|
-
channels: ch.key,
|
|
234
|
-
authorities: 10,
|
|
235
|
-
enableAutoCommit: true,
|
|
236
|
-
});
|
|
237
|
-
const w2 = await client.openWriter({
|
|
238
|
-
start: 0,
|
|
239
|
-
channels: ch.key,
|
|
240
|
-
authorities: 20,
|
|
241
|
-
enableAutoCommit: true,
|
|
254
|
+
const [index, data] = channels;
|
|
255
|
+
await w1.write({
|
|
256
|
+
[index.key]: secondsLinspace(5, 10),
|
|
257
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
242
258
|
});
|
|
243
|
-
|
|
244
|
-
await w1.write(ch.key, randomSeries(10, ch.dataType));
|
|
245
|
-
let f = await ch.read(TimeRange.MAX);
|
|
259
|
+
let f = await index.read(TimeRange.MAX);
|
|
246
260
|
expect(f.length).toEqual(0);
|
|
247
261
|
|
|
248
|
-
await w1.setAuthority(
|
|
249
|
-
await w1.write(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
expect(f.length).toEqual(10);
|
|
253
|
-
|
|
254
|
-
await w1.close();
|
|
255
|
-
await w2.close();
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("setAuthority on all channels", async () => {
|
|
259
|
-
const ch = await newChannel();
|
|
260
|
-
const w1 = await client.openWriter({
|
|
261
|
-
start: 0,
|
|
262
|
-
channels: ch.key,
|
|
263
|
-
authorities: 10,
|
|
264
|
-
enableAutoCommit: true,
|
|
265
|
-
});
|
|
266
|
-
const w2 = await client.openWriter({
|
|
267
|
-
start: 0,
|
|
268
|
-
channels: ch.key,
|
|
269
|
-
authorities: 20,
|
|
270
|
-
enableAutoCommit: true,
|
|
262
|
+
await w1.setAuthority({ [index.name]: 100, [data.name]: 100 });
|
|
263
|
+
await w1.write({
|
|
264
|
+
[index.key]: secondsLinspace(5, 10),
|
|
265
|
+
[data.key]: randomSeries(10, data.dataType),
|
|
271
266
|
});
|
|
272
|
-
|
|
273
|
-
await w1.write(ch.key, randomSeries(10, ch.dataType));
|
|
274
|
-
let f = await ch.read(TimeRange.MAX);
|
|
275
|
-
expect(f.length).toEqual(0);
|
|
276
|
-
|
|
277
|
-
await w1.setAuthority(ch.name, 255);
|
|
278
|
-
await w1.write(ch.key, randomSeries(10, ch.dataType));
|
|
279
|
-
|
|
280
|
-
f = await ch.read(TimeRange.MAX);
|
|
281
|
-
expect(f.length).toEqual(10);
|
|
282
|
-
|
|
283
267
|
await w1.close();
|
|
284
268
|
await w2.close();
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
describe("Client", () => {
|
|
289
|
-
test("Client - basic write", async () => {
|
|
290
|
-
const ch = await newChannel();
|
|
291
|
-
const data = randomSeries(10, ch.dataType);
|
|
292
|
-
await client.write(TimeStamp.seconds(1), ch.key, data);
|
|
293
|
-
const res = await client.read(TimeRange.MAX, ch.key);
|
|
294
|
-
expect(res.length).toEqual(data.length);
|
|
295
|
-
expect(res.data).toEqual(data);
|
|
269
|
+
f = await index.read(TimeRange.MAX);
|
|
270
|
+
expect(f.length).toEqual(10);
|
|
296
271
|
});
|
|
297
272
|
});
|
|
298
273
|
});
|
package/src/framer/writer.ts
CHANGED
|
@@ -7,13 +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 {
|
|
11
|
-
|
|
12
|
-
errorZ,
|
|
13
|
-
type Stream,
|
|
14
|
-
type StreamClient,
|
|
15
|
-
} from "@synnaxlabs/freighter";
|
|
16
|
-
import { control } from "@synnaxlabs/x";
|
|
10
|
+
import { EOF, type Stream, type WebSocketClient } from "@synnaxlabs/freighter";
|
|
11
|
+
import { control, errors } from "@synnaxlabs/x";
|
|
17
12
|
import {
|
|
18
13
|
type CrudeSeries,
|
|
19
14
|
type CrudeTimeStamp,
|
|
@@ -24,16 +19,16 @@ import { toArray } from "@synnaxlabs/x/toArray";
|
|
|
24
19
|
import { z } from "zod";
|
|
25
20
|
|
|
26
21
|
import { channel } from "@/channel";
|
|
22
|
+
import { SynnaxError } from "@/errors";
|
|
27
23
|
import { WriteAdapter } from "@/framer/adapter";
|
|
24
|
+
import { WSWriterCodec } from "@/framer/codec";
|
|
28
25
|
import { type Crude, frameZ } from "@/framer/frame";
|
|
29
|
-
import { StreamProxy } from "@/framer/streamProxy";
|
|
30
26
|
|
|
31
|
-
enum
|
|
27
|
+
export enum WriterCommand {
|
|
32
28
|
Open = 0,
|
|
33
29
|
Write = 1,
|
|
34
30
|
Commit = 2,
|
|
35
|
-
|
|
36
|
-
SetAuthority = 4,
|
|
31
|
+
SetAuthority = 3,
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
export enum WriterMode {
|
|
@@ -60,12 +55,18 @@ const constructWriterMode = (mode: CrudeWriterMode): WriterMode => {
|
|
|
60
55
|
|
|
61
56
|
export const ALWAYS_INDEX_PERSIST_ON_AUTO_COMMIT: TimeSpan = new TimeSpan(-1);
|
|
62
57
|
|
|
58
|
+
export class WriterClosedError extends SynnaxError.sub("writer_closed") {
|
|
59
|
+
constructor() {
|
|
60
|
+
super("WriterClosed");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
63
64
|
const netConfigZ = z.object({
|
|
64
65
|
start: TimeStamp.z.optional(),
|
|
65
66
|
controlSubject: control.subjectZ.optional(),
|
|
66
67
|
keys: channel.keyZ.array().optional(),
|
|
67
|
-
authorities: control.
|
|
68
|
-
mode: z.
|
|
68
|
+
authorities: control.authorityZ.array().optional(),
|
|
69
|
+
mode: z.enum(WriterMode).optional(),
|
|
69
70
|
errOnUnauthorized: z.boolean().optional(),
|
|
70
71
|
enableAutoCommit: z.boolean().optional(),
|
|
71
72
|
autoIndexPersistInterval: TimeSpan.z.optional(),
|
|
@@ -74,17 +75,18 @@ const netConfigZ = z.object({
|
|
|
74
75
|
interface Config extends z.infer<typeof netConfigZ> {}
|
|
75
76
|
|
|
76
77
|
const reqZ = z.object({
|
|
77
|
-
command: z.
|
|
78
|
+
command: z.enum(WriterCommand),
|
|
78
79
|
config: netConfigZ.optional(),
|
|
79
80
|
frame: frameZ.optional(),
|
|
81
|
+
buffer: z.instanceof(Uint8Array).optional(),
|
|
80
82
|
});
|
|
81
83
|
|
|
82
|
-
interface
|
|
84
|
+
export interface WriteRequest extends z.infer<typeof reqZ> {}
|
|
83
85
|
|
|
84
86
|
const resZ = z.object({
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
command: z.enum(WriterCommand),
|
|
88
|
+
end: TimeStamp.z,
|
|
89
|
+
err: errors.payloadZ.optional(),
|
|
88
90
|
});
|
|
89
91
|
|
|
90
92
|
interface Response extends z.infer<typeof resZ> {}
|
|
@@ -114,6 +116,7 @@ export interface WriterConfig {
|
|
|
114
116
|
// persisted. To persist every commit to guarantee minimal loss of data, set
|
|
115
117
|
// auto_index_persist_interval to AlwaysAutoIndexPersist.
|
|
116
118
|
autoIndexPersistInterval?: TimeSpan;
|
|
119
|
+
useExperimentalCodec?: boolean;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
/**
|
|
@@ -156,34 +159,37 @@ export interface WriterConfig {
|
|
|
156
159
|
*/
|
|
157
160
|
export class Writer {
|
|
158
161
|
private static readonly ENDPOINT = "/frame/write";
|
|
159
|
-
private readonly stream:
|
|
162
|
+
private readonly stream: Stream<typeof reqZ, typeof resZ>;
|
|
160
163
|
private readonly adapter: WriteAdapter;
|
|
161
|
-
private
|
|
164
|
+
private closeErr: Error | null = null;
|
|
162
165
|
|
|
163
166
|
private constructor(stream: Stream<typeof reqZ, typeof resZ>, adapter: WriteAdapter) {
|
|
164
|
-
this.stream =
|
|
167
|
+
this.stream = stream;
|
|
165
168
|
this.adapter = adapter;
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
static async _open(
|
|
169
172
|
retriever: channel.Retriever,
|
|
170
|
-
client:
|
|
173
|
+
client: WebSocketClient,
|
|
171
174
|
{
|
|
172
175
|
channels,
|
|
173
176
|
start = TimeStamp.now(),
|
|
174
|
-
authorities = control.
|
|
177
|
+
authorities = control.ABSOLUTE_AUTHORITY,
|
|
175
178
|
controlSubject: subject,
|
|
176
179
|
mode = WriterMode.PersistStream,
|
|
177
180
|
errOnUnauthorized = false,
|
|
178
181
|
enableAutoCommit = false,
|
|
179
182
|
autoIndexPersistInterval = TimeSpan.SECOND,
|
|
183
|
+
useExperimentalCodec = true,
|
|
180
184
|
}: WriterConfig,
|
|
181
185
|
): Promise<Writer> {
|
|
182
186
|
const adapter = await WriteAdapter.open(retriever, channels);
|
|
187
|
+
if (useExperimentalCodec)
|
|
188
|
+
client = client.withCodec(new WSWriterCodec(adapter.codec));
|
|
183
189
|
const stream = await client.stream(Writer.ENDPOINT, reqZ, resZ);
|
|
184
190
|
const writer = new Writer(stream, adapter);
|
|
185
191
|
await writer.execute({
|
|
186
|
-
command:
|
|
192
|
+
command: WriterCommand.Open,
|
|
187
193
|
config: {
|
|
188
194
|
start: new TimeStamp(start),
|
|
189
195
|
keys: adapter.keys,
|
|
@@ -198,21 +204,13 @@ export class Writer {
|
|
|
198
204
|
return writer;
|
|
199
205
|
}
|
|
200
206
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
while (this.stream.received()) await this.stream.receive();
|
|
205
|
-
}
|
|
206
|
-
return this.errAccumulated;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async write(channel: channel.KeyOrName, data: CrudeSeries): Promise<boolean>;
|
|
210
|
-
async write(channel: channel.KeysOrNames, data: CrudeSeries[]): Promise<boolean>;
|
|
211
|
-
async write(frame: Crude | Record<channel.KeyOrName, CrudeSeries>): Promise<boolean>;
|
|
207
|
+
async write(channel: channel.KeyOrName, data: CrudeSeries): Promise<void>;
|
|
208
|
+
async write(channel: channel.KeysOrNames, data: CrudeSeries[]): Promise<void>;
|
|
209
|
+
async write(frame: Crude | Record<channel.KeyOrName, CrudeSeries>): Promise<void>;
|
|
212
210
|
async write(
|
|
213
211
|
channelsOrData: channel.Params | Record<channel.KeyOrName, CrudeSeries> | Crude,
|
|
214
212
|
series?: CrudeSeries | CrudeSeries[],
|
|
215
|
-
): Promise<
|
|
213
|
+
): Promise<void>;
|
|
216
214
|
|
|
217
215
|
/**
|
|
218
216
|
* Writes the given frame to the database.
|
|
@@ -231,29 +229,29 @@ export class Writer {
|
|
|
231
229
|
async write(
|
|
232
230
|
channelsOrData: channel.Params | Record<channel.KeyOrName, CrudeSeries> | Crude,
|
|
233
231
|
series?: CrudeSeries | CrudeSeries[],
|
|
234
|
-
): Promise<
|
|
235
|
-
if (
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
if (this.closeErr != null) throw this.closeErr;
|
|
234
|
+
if (this.stream.received()) return await this.close();
|
|
236
235
|
const frame = await this.adapter.adapt(channelsOrData, series);
|
|
237
|
-
this.stream.send({ command:
|
|
238
|
-
return true;
|
|
236
|
+
this.stream.send({ command: WriterCommand.Write, frame: frame.toPayload() });
|
|
239
237
|
}
|
|
240
238
|
|
|
241
|
-
async setAuthority(value: number): Promise<
|
|
239
|
+
async setAuthority(value: number): Promise<void>;
|
|
242
240
|
|
|
243
241
|
async setAuthority(
|
|
244
242
|
key: channel.KeyOrName,
|
|
245
243
|
authority: control.Authority,
|
|
246
|
-
): Promise<
|
|
244
|
+
): Promise<void>;
|
|
247
245
|
|
|
248
246
|
async setAuthority(
|
|
249
247
|
value: Record<channel.KeyOrName, control.Authority>,
|
|
250
|
-
): Promise<
|
|
248
|
+
): Promise<void>;
|
|
251
249
|
|
|
252
250
|
async setAuthority(
|
|
253
251
|
value: Record<channel.KeyOrName, control.Authority> | channel.KeyOrName | number,
|
|
254
252
|
authority?: control.Authority,
|
|
255
|
-
): Promise<
|
|
256
|
-
if (
|
|
253
|
+
): Promise<void> {
|
|
254
|
+
if (this.closeErr != null) throw this.closeErr;
|
|
257
255
|
let config: Config;
|
|
258
256
|
if (typeof value === "number" && authority == null)
|
|
259
257
|
config = { keys: [], authorities: [value] };
|
|
@@ -268,8 +266,7 @@ export class Writer {
|
|
|
268
266
|
authorities: Object.values(oValue),
|
|
269
267
|
};
|
|
270
268
|
}
|
|
271
|
-
|
|
272
|
-
return response.ack;
|
|
269
|
+
await this.execute({ command: WriterCommand.SetAuthority, config });
|
|
273
270
|
}
|
|
274
271
|
|
|
275
272
|
/**
|
|
@@ -280,19 +277,14 @@ export class Writer {
|
|
|
280
277
|
* should acknowledge the error by calling the error method or closing the writer.
|
|
281
278
|
* After the caller acknowledges the error, they can attempt to commit again.
|
|
282
279
|
*/
|
|
283
|
-
async commit(): Promise<
|
|
284
|
-
if (
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
* state, allowing the writer to be used again.
|
|
292
|
-
*/
|
|
293
|
-
async error(): Promise<Error | null> {
|
|
294
|
-
const res = await this.execute({ command: Command.Error });
|
|
295
|
-
return res.error != null ? decodeError(res.error) : null;
|
|
280
|
+
async commit(): Promise<TimeStamp> {
|
|
281
|
+
if (this.closeErr != null) throw this.closeErr;
|
|
282
|
+
if (this.stream.received()) {
|
|
283
|
+
await this.closeInternal(null);
|
|
284
|
+
return TimeStamp.ZERO;
|
|
285
|
+
}
|
|
286
|
+
const res = await this.execute({ command: WriterCommand.Commit });
|
|
287
|
+
return res.end;
|
|
296
288
|
}
|
|
297
289
|
|
|
298
290
|
/**
|
|
@@ -301,19 +293,33 @@ export class Writer {
|
|
|
301
293
|
* in a 'finally' block.
|
|
302
294
|
*/
|
|
303
295
|
async close(): Promise<void> {
|
|
304
|
-
await this.
|
|
296
|
+
await this.closeInternal(null);
|
|
305
297
|
}
|
|
306
298
|
|
|
307
|
-
async
|
|
308
|
-
this.
|
|
299
|
+
private async closeInternal(err: Error | null): Promise<null> {
|
|
300
|
+
if (this.closeErr != null) throw this.closeErr;
|
|
301
|
+
this.closeErr = err;
|
|
302
|
+
this.stream.closeSend();
|
|
309
303
|
while (true) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
304
|
+
if (this.closeErr != null) {
|
|
305
|
+
if (WriterClosedError.matches(this.closeErr)) return null;
|
|
306
|
+
throw this.closeErr;
|
|
307
|
+
}
|
|
308
|
+
const [res, err] = await this.stream.receive();
|
|
309
|
+
if (err != null) this.closeErr = EOF.matches(err) ? new WriterClosedError() : err;
|
|
310
|
+
else this.closeErr = errors.decode(res?.err);
|
|
313
311
|
}
|
|
314
312
|
}
|
|
315
313
|
|
|
316
|
-
private
|
|
317
|
-
|
|
314
|
+
private async execute(req: WriteRequest): Promise<Response> {
|
|
315
|
+
const err = this.stream.send(req);
|
|
316
|
+
if (err != null) await this.closeInternal(err);
|
|
317
|
+
while (true) {
|
|
318
|
+
const [res, err] = await this.stream.receive();
|
|
319
|
+
if (err != null) await this.closeInternal(err);
|
|
320
|
+
const resErr = errors.decode(res?.err);
|
|
321
|
+
if (resErr != null) await this.closeInternal(resErr);
|
|
322
|
+
if (res?.command == req.command) return res;
|
|
323
|
+
}
|
|
318
324
|
}
|
|
319
325
|
}
|