@synnaxlabs/client 0.55.0 → 0.56.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 +10 -13
- package/dist/client.cjs +60 -36
- package/dist/client.js +6435 -4786
- package/dist/src/access/policy/client.d.ts +70 -80
- package/dist/src/access/policy/client.d.ts.map +1 -1
- package/dist/src/access/policy/types.gen.d.ts +18 -20
- package/dist/src/access/policy/types.gen.d.ts.map +1 -1
- package/dist/src/access/role/client.d.ts.map +1 -1
- package/dist/src/access/role/types.gen.d.ts +2 -2
- package/dist/src/actions/actions.d.ts +68 -0
- package/dist/src/actions/actions.d.ts.map +1 -0
- package/dist/src/actions/actions.spec.d.ts +2 -0
- package/dist/src/actions/actions.spec.d.ts.map +1 -0
- package/dist/src/actions/external.d.ts +2 -0
- package/dist/src/actions/external.d.ts.map +1 -0
- package/dist/src/actions/index.d.ts +2 -0
- package/dist/src/actions/index.d.ts.map +1 -0
- package/dist/src/arc/client.d.ts.map +1 -1
- package/dist/src/arc/compiler/types.gen.d.ts +1 -1
- package/dist/src/arc/compiler/types.gen.d.ts.map +1 -1
- package/dist/src/arc/graph/types.gen.d.ts +29 -29
- package/dist/src/arc/graph/types.gen.d.ts.map +1 -1
- package/dist/src/arc/ir/types.gen.d.ts +123 -123
- package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
- package/dist/src/arc/module/types.gen.d.ts +45 -45
- package/dist/src/arc/program/types.gen.d.ts +45 -45
- package/dist/src/arc/types/types.gen.d.ts +11 -11
- package/dist/src/arc/types/types.gen.d.ts.map +1 -1
- package/dist/src/arc/types.gen.d.ts +99 -99
- package/dist/src/auth/auth.d.ts +3 -3
- package/dist/src/auth/auth.d.ts.map +1 -1
- package/dist/src/channel/client.d.ts +2 -2
- package/dist/src/channel/client.d.ts.map +1 -1
- package/dist/src/channel/retriever.d.ts +5 -8
- package/dist/src/channel/retriever.d.ts.map +1 -1
- package/dist/src/channel/types.gen.d.ts +3 -3
- package/dist/src/channel/writer.d.ts.map +1 -1
- package/dist/src/connection/checker.d.ts +1 -1
- package/dist/src/connection/checker.d.ts.map +1 -1
- package/dist/src/device/client.d.ts.map +1 -1
- package/dist/src/device/types.gen.d.ts +6 -8
- package/dist/src/device/types.gen.d.ts.map +1 -1
- package/dist/src/errors.d.ts +2 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/framer/adapter.d.ts.map +1 -1
- package/dist/src/framer/client.d.ts +2 -2
- package/dist/src/framer/codec.d.ts +9 -1
- package/dist/src/framer/codec.d.ts.map +1 -1
- package/dist/src/framer/deleter.d.ts.map +1 -1
- package/dist/src/framer/frame.d.ts +1 -1
- package/dist/src/framer/iterator.d.ts +84 -3
- package/dist/src/framer/iterator.d.ts.map +1 -1
- package/dist/src/framer/streamProxy.d.ts.map +1 -1
- package/dist/src/framer/streamer.d.ts +1 -3
- package/dist/src/framer/streamer.d.ts.map +1 -1
- package/dist/src/framer/types.gen.d.ts +18 -0
- package/dist/src/framer/types.gen.d.ts.map +1 -1
- package/dist/src/framer/writer.d.ts +8 -8
- package/dist/src/framer/writer.d.ts.map +1 -1
- package/dist/src/group/client.d.ts +1 -2
- package/dist/src/group/client.d.ts.map +1 -1
- package/dist/src/group/types.gen.d.ts +2 -2
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/label/client.d.ts +5 -8
- package/dist/src/label/client.d.ts.map +1 -1
- package/dist/src/lineplot/client.d.ts.map +1 -1
- package/dist/src/lineplot/types.gen.d.ts +2 -2
- package/dist/src/log/client.d.ts.map +1 -1
- package/dist/src/log/types.gen.d.ts +2 -2
- package/dist/src/ontology/client.d.ts +1 -3
- package/dist/src/ontology/client.d.ts.map +1 -1
- package/dist/src/ontology/payload.d.ts +12 -16
- package/dist/src/ontology/payload.d.ts.map +1 -1
- package/dist/src/ontology/types.gen.d.ts +1 -2
- package/dist/src/ontology/types.gen.d.ts.map +1 -1
- package/dist/src/ontology/writer.d.ts +5 -10
- package/dist/src/ontology/writer.d.ts.map +1 -1
- package/dist/src/rack/client.d.ts.map +1 -1
- package/dist/src/rack/types.gen.d.ts +3 -3
- package/dist/src/ranger/alias/client.d.ts.map +1 -1
- package/dist/src/ranger/client.d.ts.map +1 -1
- package/dist/src/ranger/kv/client.d.ts.map +1 -1
- package/dist/src/ranger/types.gen.d.ts +6 -6
- package/dist/src/ranger/types.gen.d.ts.map +1 -1
- package/dist/src/ranger/writer.d.ts +2 -3
- package/dist/src/ranger/writer.d.ts.map +1 -1
- package/dist/src/schematic/actions.d.ts +147 -0
- package/dist/src/schematic/actions.d.ts.map +1 -0
- package/dist/src/schematic/actions.gen.d.ts +484 -0
- package/dist/src/schematic/actions.gen.d.ts.map +1 -0
- package/dist/src/schematic/actions.spec.d.ts +2 -0
- package/dist/src/schematic/actions.spec.d.ts.map +1 -0
- package/dist/src/schematic/client.d.ts +53 -2
- package/dist/src/schematic/client.d.ts.map +1 -1
- package/dist/src/schematic/external.d.ts +2 -0
- package/dist/src/schematic/external.d.ts.map +1 -1
- package/dist/src/schematic/symbol/client.d.ts.map +1 -1
- package/dist/src/schematic/symbol/types.gen.d.ts +48 -58
- package/dist/src/schematic/symbol/types.gen.d.ts.map +1 -1
- package/dist/src/schematic/types.gen.d.ts +131 -5
- package/dist/src/schematic/types.gen.d.ts.map +1 -1
- package/dist/src/status/client.d.ts.map +1 -1
- package/dist/src/status/payload.d.ts +3 -3
- package/dist/src/table/actions.d.ts +156 -0
- package/dist/src/table/actions.d.ts.map +1 -0
- package/dist/src/table/actions.gen.d.ts +587 -0
- package/dist/src/table/actions.gen.d.ts.map +1 -0
- package/dist/src/table/client.d.ts +28 -2
- package/dist/src/table/client.d.ts.map +1 -1
- package/dist/src/table/external.d.ts +2 -0
- package/dist/src/table/external.d.ts.map +1 -1
- package/dist/src/table/types.gen.d.ts +71 -4
- package/dist/src/table/types.gen.d.ts.map +1 -1
- package/dist/src/task/client.d.ts.map +1 -1
- package/dist/src/task/types.gen.d.ts +7 -7
- package/dist/src/task/types.gen.d.ts.map +1 -1
- package/dist/src/user/client.d.ts +2 -2
- package/dist/src/user/client.d.ts.map +1 -1
- package/dist/src/user/types.gen.d.ts +2 -2
- package/dist/src/view/client.d.ts.map +1 -1
- package/dist/src/view/types.gen.d.ts +2 -2
- package/dist/src/workspace/client.d.ts.map +1 -1
- package/dist/src/workspace/types.gen.d.ts +3 -3
- package/dist/src/workspace/types.gen.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/access/policy/client.ts +4 -7
- package/src/access/role/client.ts +6 -26
- package/src/actions/actions.spec.ts +229 -0
- package/src/actions/actions.ts +104 -0
- package/src/actions/external.ts +10 -0
- package/src/actions/index.ts +10 -0
- package/src/arc/client.ts +3 -7
- package/src/arc/compiler/types.gen.ts +2 -1
- package/src/arc/ir/types.gen.ts +2 -2
- package/src/arc/lsp.spec.ts +3 -7
- package/src/arc/types/types.gen.ts +3 -3
- package/src/auth/auth.spec.ts +12 -13
- package/src/auth/auth.ts +36 -34
- package/src/channel/batchRetriever.spec.ts +13 -4
- package/src/channel/client.ts +8 -6
- package/src/channel/retriever.ts +7 -16
- package/src/channel/writer.ts +4 -20
- package/src/connection/checker.ts +6 -6
- package/src/connection/connection.spec.ts +5 -8
- package/src/device/client.ts +5 -8
- package/src/device/types.gen.ts +4 -4
- package/src/errors.ts +9 -9
- package/src/framer/adapter.ts +2 -4
- package/src/framer/client.ts +1 -1
- package/src/framer/codec.spec.ts +53 -3
- package/src/framer/codec.ts +58 -25
- package/src/framer/deleter.ts +2 -8
- package/src/framer/iterator.ts +43 -40
- package/src/framer/streamProxy.ts +13 -13
- package/src/framer/streamer.spec.ts +12 -3
- package/src/framer/streamer.ts +7 -12
- package/src/framer/types.gen.ts +20 -0
- package/src/framer/writer.spec.ts +77 -0
- package/src/framer/writer.ts +51 -28
- package/src/group/client.ts +4 -7
- package/src/index.ts +3 -2
- package/src/label/client.ts +6 -16
- package/src/lineplot/client.ts +6 -21
- package/src/log/client.ts +6 -21
- package/src/ontology/client.ts +3 -4
- package/src/ontology/types.gen.ts +0 -1
- package/src/ontology/writer.ts +4 -7
- package/src/rack/client.ts +4 -7
- package/src/ranger/alias/client.ts +6 -11
- package/src/ranger/client.ts +3 -4
- package/src/ranger/kv/client.ts +5 -8
- package/src/ranger/writer.ts +4 -17
- package/src/schematic/access.spec.ts +6 -6
- package/src/schematic/actions.gen.ts +200 -0
- package/src/schematic/actions.spec.ts +699 -0
- package/src/schematic/actions.ts +168 -0
- package/src/schematic/client.ts +34 -30
- package/src/schematic/external.ts +2 -0
- package/src/schematic/schematic.spec.ts +233 -69
- package/src/schematic/symbol/client.ts +6 -11
- package/src/schematic/symbol/types.gen.ts +1 -10
- package/src/schematic/types.gen.ts +55 -6
- package/src/status/client.ts +4 -10
- package/src/table/access.spec.ts +0 -6
- package/src/table/actions.gen.ts +243 -0
- package/src/table/actions.ts +255 -0
- package/src/table/client.ts +21 -25
- package/src/table/external.ts +2 -0
- package/src/table/table.spec.ts +588 -43
- package/src/table/types.gen.ts +58 -5
- package/src/task/client.ts +7 -11
- package/src/task/types.gen.ts +8 -6
- package/src/user/client.ts +6 -11
- package/src/view/client.ts +4 -7
- package/src/workspace/client.ts +6 -16
- package/src/workspace/types.gen.ts +2 -1
package/src/framer/iterator.ts
CHANGED
|
@@ -7,7 +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 { type Stream, type
|
|
10
|
+
import { type Stream, type WebSocketClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import {
|
|
12
12
|
type CrudeTimeRange,
|
|
13
13
|
type CrudeTimeSpan,
|
|
@@ -21,31 +21,15 @@ import { z } from "zod";
|
|
|
21
21
|
|
|
22
22
|
import { channel } from "@/channel";
|
|
23
23
|
import { ReadAdapter } from "@/framer/adapter";
|
|
24
|
+
import { WSIteratorCodec } from "@/framer/codec";
|
|
24
25
|
import { Frame, frameZ } from "@/framer/frame";
|
|
25
26
|
import { StreamProxy } from "@/framer/streamProxy";
|
|
27
|
+
import { IteratorCommand, IteratorResponseVariant } from "@/framer/types.gen";
|
|
26
28
|
|
|
27
29
|
export const AUTO_SPAN = new TimeSpan(-1);
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Next = 1,
|
|
32
|
-
Prev = 2,
|
|
33
|
-
SeekFirst = 3,
|
|
34
|
-
SeekLast = 4,
|
|
35
|
-
SeekLE = 5,
|
|
36
|
-
SeekGE = 6,
|
|
37
|
-
Valid = 7,
|
|
38
|
-
Error = 8,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
enum ResponseVariant {
|
|
42
|
-
None = 0,
|
|
43
|
-
Ack = 1,
|
|
44
|
-
Data = 2,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const reqZ = z.object({
|
|
48
|
-
command: z.enum(Command),
|
|
31
|
+
export const iteratorReqZ = z.object({
|
|
32
|
+
command: z.enum(IteratorCommand),
|
|
49
33
|
span: TimeSpan.z.optional(),
|
|
50
34
|
bounds: TimeRange.z.optional(),
|
|
51
35
|
stamp: TimeStamp.z.optional(),
|
|
@@ -53,16 +37,19 @@ const reqZ = z.object({
|
|
|
53
37
|
chunkSize: z.number().optional(),
|
|
54
38
|
downsampleFactor: z.int().optional(),
|
|
55
39
|
});
|
|
56
|
-
interface Request extends z.infer<typeof reqZ> {}
|
|
57
40
|
|
|
58
|
-
|
|
59
|
-
|
|
41
|
+
export interface IteratorRequest extends z.infer<typeof iteratorReqZ> {}
|
|
42
|
+
|
|
43
|
+
export const iteratorResZ = z.object({
|
|
44
|
+
variant: z.enum(IteratorResponseVariant),
|
|
60
45
|
ack: z.boolean(),
|
|
61
|
-
command: z.enum(
|
|
46
|
+
command: z.enum(IteratorCommand),
|
|
62
47
|
error: errors.payloadZ.optional().nullable(),
|
|
63
48
|
frame: frameZ.optional(),
|
|
64
49
|
});
|
|
65
50
|
|
|
51
|
+
export interface IteratorResponse extends z.infer<typeof iteratorResZ> {}
|
|
52
|
+
|
|
66
53
|
export interface IteratorConfig {
|
|
67
54
|
/** chunkSize is the maximum number of samples contained per channel in the frame
|
|
68
55
|
* resulting from a call to next with {@link AUTO_SPAN}.
|
|
@@ -84,11 +71,14 @@ export interface IteratorConfig {
|
|
|
84
71
|
* telemetry between two timestamps, see the SegmentClient.read method.
|
|
85
72
|
*/
|
|
86
73
|
export class Iterator {
|
|
87
|
-
private readonly stream: StreamProxy<typeof
|
|
74
|
+
private readonly stream: StreamProxy<typeof iteratorReqZ, typeof iteratorResZ>;
|
|
88
75
|
private readonly adapter: ReadAdapter;
|
|
89
76
|
value: Frame;
|
|
90
77
|
|
|
91
|
-
private constructor(
|
|
78
|
+
private constructor(
|
|
79
|
+
stream: Stream<typeof iteratorReqZ, typeof iteratorResZ>,
|
|
80
|
+
adapter: ReadAdapter,
|
|
81
|
+
) {
|
|
92
82
|
this.stream = new StreamProxy("Iterator", stream);
|
|
93
83
|
this.value = new Frame();
|
|
94
84
|
this.adapter = adapter;
|
|
@@ -109,14 +99,15 @@ export class Iterator {
|
|
|
109
99
|
tr: CrudeTimeRange,
|
|
110
100
|
channels: channel.Params,
|
|
111
101
|
retriever: channel.Retriever,
|
|
112
|
-
client:
|
|
102
|
+
client: WebSocketClient,
|
|
113
103
|
opts: IteratorConfig = {},
|
|
114
104
|
): Promise<Iterator> {
|
|
115
105
|
const adapter = await ReadAdapter.open(retriever, channels);
|
|
116
|
-
|
|
106
|
+
client = client.withCodec(new WSIteratorCodec(adapter.codec));
|
|
107
|
+
const stream = await client.stream("/frame/iterate", iteratorReqZ, iteratorResZ);
|
|
117
108
|
const iter = new Iterator(stream, adapter);
|
|
118
109
|
await iter.execute({
|
|
119
|
-
command:
|
|
110
|
+
command: IteratorCommand.Open,
|
|
120
111
|
keys: Array.from(adapter.keys),
|
|
121
112
|
bounds: new TimeRange(tr),
|
|
122
113
|
chunkSize: opts.chunkSize ?? 1e5,
|
|
@@ -137,7 +128,10 @@ export class Iterator {
|
|
|
137
128
|
* particular channel or the iterator has accumulated an error.
|
|
138
129
|
*/
|
|
139
130
|
async next(span: CrudeTimeSpan = AUTO_SPAN): Promise<boolean> {
|
|
140
|
-
return await this.execute({
|
|
131
|
+
return await this.execute({
|
|
132
|
+
command: IteratorCommand.Next,
|
|
133
|
+
span: new TimeSpan(span),
|
|
134
|
+
});
|
|
141
135
|
}
|
|
142
136
|
|
|
143
137
|
/**
|
|
@@ -152,7 +146,10 @@ export class Iterator {
|
|
|
152
146
|
* channel or the iterator has accumulated an error.
|
|
153
147
|
*/
|
|
154
148
|
async prev(span: CrudeTimeSpan = AUTO_SPAN): Promise<boolean> {
|
|
155
|
-
return await this.execute({
|
|
149
|
+
return await this.execute({
|
|
150
|
+
command: IteratorCommand.Prev,
|
|
151
|
+
span: new TimeSpan(span),
|
|
152
|
+
});
|
|
156
153
|
}
|
|
157
154
|
|
|
158
155
|
/**
|
|
@@ -164,7 +161,7 @@ export class Iterator {
|
|
|
164
161
|
* channel or has accumulated an error.
|
|
165
162
|
*/
|
|
166
163
|
async seekFirst(): Promise<boolean> {
|
|
167
|
-
return await this.execute({ command:
|
|
164
|
+
return await this.execute({ command: IteratorCommand.SeekFirst });
|
|
168
165
|
}
|
|
169
166
|
|
|
170
167
|
/** Seeks the iterator to the last segment in the time range, but does not read it.
|
|
@@ -175,7 +172,7 @@ export class Iterator {
|
|
|
175
172
|
* channel or has accumulated an error.
|
|
176
173
|
*/
|
|
177
174
|
async seekLast(): Promise<boolean> {
|
|
178
|
-
return await this.execute({ command:
|
|
175
|
+
return await this.execute({ command: IteratorCommand.SeekLast });
|
|
179
176
|
}
|
|
180
177
|
|
|
181
178
|
/**
|
|
@@ -187,7 +184,10 @@ export class Iterator {
|
|
|
187
184
|
* channel or has accumulated an error.
|
|
188
185
|
*/
|
|
189
186
|
async seekLE(stamp: CrudeTimeStamp): Promise<boolean> {
|
|
190
|
-
return await this.execute({
|
|
187
|
+
return await this.execute({
|
|
188
|
+
command: IteratorCommand.SeekLE,
|
|
189
|
+
stamp: new TimeStamp(stamp),
|
|
190
|
+
});
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/**
|
|
@@ -199,7 +199,10 @@ export class Iterator {
|
|
|
199
199
|
* channel or has accumulated an error.
|
|
200
200
|
*/
|
|
201
201
|
async seekGE(stamp: CrudeTimeStamp): Promise<boolean> {
|
|
202
|
-
return await this.execute({
|
|
202
|
+
return await this.execute({
|
|
203
|
+
command: IteratorCommand.SeekGE,
|
|
204
|
+
stamp: new TimeStamp(stamp),
|
|
205
|
+
});
|
|
203
206
|
}
|
|
204
207
|
|
|
205
208
|
/**
|
|
@@ -208,7 +211,7 @@ export class Iterator {
|
|
|
208
211
|
* an error.
|
|
209
212
|
*/
|
|
210
213
|
async valid(): Promise<boolean> {
|
|
211
|
-
return await this.execute({ command:
|
|
214
|
+
return await this.execute({ command: IteratorCommand.Valid });
|
|
212
215
|
}
|
|
213
216
|
|
|
214
217
|
/**
|
|
@@ -224,12 +227,12 @@ export class Iterator {
|
|
|
224
227
|
return new IteratorIterator(this);
|
|
225
228
|
}
|
|
226
229
|
|
|
227
|
-
private async execute(request:
|
|
230
|
+
private async execute(request: IteratorRequest): Promise<boolean> {
|
|
228
231
|
this.stream.send(request);
|
|
229
232
|
this.value = new Frame();
|
|
230
233
|
while (true) {
|
|
231
234
|
const res = await this.stream.receive();
|
|
232
|
-
if (res.variant ===
|
|
235
|
+
if (res.variant === IteratorResponseVariant.Ack) return res.ack;
|
|
233
236
|
this.value.push(this.adapter.adapt(new Frame(res.frame)));
|
|
234
237
|
}
|
|
235
238
|
}
|
|
@@ -255,7 +258,7 @@ class IteratorIterator implements AsyncIterator<Frame> {
|
|
|
255
258
|
return { done: !ok, value: this.iter.value };
|
|
256
259
|
} catch (e) {
|
|
257
260
|
await this.iter.close();
|
|
258
|
-
throw e;
|
|
261
|
+
throw errors.fromUnknown(e);
|
|
259
262
|
}
|
|
260
263
|
}
|
|
261
264
|
}
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { EOF, type Stream } from "@synnaxlabs/freighter";
|
|
11
|
+
import { errors } from "@synnaxlabs/x";
|
|
11
12
|
import { type z } from "zod";
|
|
12
13
|
|
|
14
|
+
import { UnexpectedError } from "@/errors";
|
|
15
|
+
|
|
13
16
|
export class StreamProxy<RQ extends z.ZodType, RS extends z.ZodType> {
|
|
14
17
|
readonly name: string;
|
|
15
18
|
private readonly stream: Stream<RQ, RS>;
|
|
@@ -20,9 +23,7 @@ export class StreamProxy<RQ extends z.ZodType, RS extends z.ZodType> {
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
async receive(): Promise<z.infer<RS>> {
|
|
23
|
-
|
|
24
|
-
if (err != null) throw err;
|
|
25
|
-
return res;
|
|
26
|
+
return await this.stream.receive();
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
received(): boolean {
|
|
@@ -32,16 +33,16 @@ export class StreamProxy<RQ extends z.ZodType, RS extends z.ZodType> {
|
|
|
32
33
|
async closeAndAck(): Promise<void> {
|
|
33
34
|
this.stream.closeSend();
|
|
34
35
|
while (true) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Please report this error to the Synnax team.`,
|
|
40
|
-
);
|
|
41
|
-
if (err != null) {
|
|
36
|
+
let res: z.infer<RS>;
|
|
37
|
+
try {
|
|
38
|
+
res = await this.stream.receive();
|
|
39
|
+
} catch (err) {
|
|
42
40
|
if (EOF.matches(err)) return;
|
|
43
|
-
throw err;
|
|
41
|
+
throw errors.fromUnknown(err);
|
|
44
42
|
}
|
|
43
|
+
throw new UnexpectedError(
|
|
44
|
+
`${this.name} received unexpected response ${JSON.stringify(res)} on closure.`,
|
|
45
|
+
);
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -50,7 +51,6 @@ export class StreamProxy<RQ extends z.ZodType, RS extends z.ZodType> {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
send(req: z.input<RQ>): void {
|
|
53
|
-
|
|
54
|
-
if (err != null) throw err;
|
|
54
|
+
this.stream.send(req);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -8,7 +8,16 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { EOF, Unreachable } from "@synnaxlabs/freighter";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
DataType,
|
|
13
|
+
errors,
|
|
14
|
+
id,
|
|
15
|
+
Rate,
|
|
16
|
+
Series,
|
|
17
|
+
sleep,
|
|
18
|
+
TimeSpan,
|
|
19
|
+
TimeStamp,
|
|
20
|
+
} from "@synnaxlabs/x";
|
|
12
21
|
import { describe, expect, it, test, vi } from "vitest";
|
|
13
22
|
|
|
14
23
|
import { type channel } from "@/channel";
|
|
@@ -552,7 +561,7 @@ describe("Streamer", () => {
|
|
|
552
561
|
return { done: false, value: fr };
|
|
553
562
|
} catch (err) {
|
|
554
563
|
if (EOF.matches(err)) return { done: true, value: undefined };
|
|
555
|
-
throw err;
|
|
564
|
+
throw errors.fromUnknown(err);
|
|
556
565
|
}
|
|
557
566
|
}
|
|
558
567
|
|
|
@@ -565,7 +574,7 @@ describe("Streamer", () => {
|
|
|
565
574
|
it("should correctly call the underlying streamer methods", async () => {
|
|
566
575
|
const streamer = new MockStreamer();
|
|
567
576
|
const openMock = vi.fn();
|
|
568
|
-
const config = { channels: [1, 2, 3]
|
|
577
|
+
const config = { channels: [1, 2, 3] };
|
|
569
578
|
const fr = new Frame({ 1: new Series([1]) });
|
|
570
579
|
const hardened = await HardenedStreamer.open(
|
|
571
580
|
async (cfg) => {
|
package/src/framer/streamer.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { EOF, type Stream, type WebSocketClient } from "@synnaxlabs/freighter";
|
|
11
|
-
import { breaker, observe, Rate, TimeSpan } from "@synnaxlabs/x";
|
|
11
|
+
import { breaker, errors, observe, Rate, TimeSpan } from "@synnaxlabs/x";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
14
14
|
import { type channel } from "@/channel";
|
|
@@ -46,9 +46,6 @@ const intermediateStreamerConfigZ = z.object({
|
|
|
46
46
|
downsampleFactor: z.int().default(1),
|
|
47
47
|
/** Optional throttle rate in Hz to limit the rate of frames sent to the client. Defaults to 0 (no throttling). */
|
|
48
48
|
throttleRate: Rate.z.default(new Rate(0)),
|
|
49
|
-
/** useHighPerformanceCodec sets whether the writer will use the Synnax frame encoder
|
|
50
|
-
as opposed to the standard JSON encoding mechanisms for frames. */
|
|
51
|
-
useHighPerformanceCodec: z.boolean().default(true),
|
|
52
49
|
/** excludeGroups sets writer group IDs whose frames should be filtered out by the
|
|
53
50
|
Core. Used for telemetry bypass deduplication. */
|
|
54
51
|
excludeGroups: z.uint32().array().default([]),
|
|
@@ -112,8 +109,7 @@ export const createStreamOpener =
|
|
|
112
109
|
async (config) => {
|
|
113
110
|
const cfg = streamerConfigZ.parse(config);
|
|
114
111
|
const adapter = await ReadAdapter.open(retriever, cfg.channels);
|
|
115
|
-
|
|
116
|
-
client = client.withCodec(new WSStreamerCodec(adapter.codec));
|
|
112
|
+
client = client.withCodec(new WSStreamerCodec(adapter.codec));
|
|
117
113
|
const stream = await client.stream("/frame/stream", reqZ, resZ);
|
|
118
114
|
const streamer = new BaseStreamer(
|
|
119
115
|
stream,
|
|
@@ -128,8 +124,7 @@ export const createStreamOpener =
|
|
|
128
124
|
throttleRate: cfg.throttleRate,
|
|
129
125
|
excludeGroups: cfg.excludeGroups,
|
|
130
126
|
});
|
|
131
|
-
|
|
132
|
-
if (err != null) throw err;
|
|
127
|
+
await stream.receive();
|
|
133
128
|
return streamer;
|
|
134
129
|
};
|
|
135
130
|
|
|
@@ -177,7 +172,7 @@ class BaseStreamer implements Streamer {
|
|
|
177
172
|
return { done: false, value: frame };
|
|
178
173
|
} catch (err) {
|
|
179
174
|
if (EOF.matches(err)) return { done: true, value: undefined };
|
|
180
|
-
throw err;
|
|
175
|
+
throw errors.fromUnknown(err);
|
|
181
176
|
}
|
|
182
177
|
}
|
|
183
178
|
|
|
@@ -256,7 +251,7 @@ export class HardenedStreamer implements Streamer {
|
|
|
256
251
|
return;
|
|
257
252
|
} catch (e) {
|
|
258
253
|
this.wrapped_ = null;
|
|
259
|
-
if (!(await this.breaker.wait())) throw e;
|
|
254
|
+
if (!(await this.breaker.wait())) throw errors.fromUnknown(e);
|
|
260
255
|
console.error("failed to open streamer", e);
|
|
261
256
|
continue;
|
|
262
257
|
}
|
|
@@ -282,7 +277,7 @@ export class HardenedStreamer implements Streamer {
|
|
|
282
277
|
return { done: false, value: await this.read() };
|
|
283
278
|
} catch (e) {
|
|
284
279
|
if (EOF.matches(e)) return { done: true, value: undefined };
|
|
285
|
-
throw e;
|
|
280
|
+
throw errors.fromUnknown(e);
|
|
286
281
|
}
|
|
287
282
|
}
|
|
288
283
|
|
|
@@ -292,7 +287,7 @@ export class HardenedStreamer implements Streamer {
|
|
|
292
287
|
this.breaker.reset();
|
|
293
288
|
return fr;
|
|
294
289
|
} catch (e) {
|
|
295
|
-
if (EOF.matches(e)) throw e;
|
|
290
|
+
if (EOF.matches(e)) throw errors.fromUnknown(e);
|
|
296
291
|
await this.runStreamer();
|
|
297
292
|
return await this.read();
|
|
298
293
|
}
|
package/src/framer/types.gen.ts
CHANGED
|
@@ -18,3 +18,23 @@ export enum WriterCommand {
|
|
|
18
18
|
SetAuthority = 3,
|
|
19
19
|
}
|
|
20
20
|
export const writerCommandZ = z.enum(WriterCommand);
|
|
21
|
+
|
|
22
|
+
export enum IteratorCommand {
|
|
23
|
+
Open = 0,
|
|
24
|
+
Next = 1,
|
|
25
|
+
Prev = 2,
|
|
26
|
+
SeekFirst = 3,
|
|
27
|
+
SeekLast = 4,
|
|
28
|
+
SeekLE = 5,
|
|
29
|
+
SeekGE = 6,
|
|
30
|
+
Valid = 7,
|
|
31
|
+
Error = 8,
|
|
32
|
+
}
|
|
33
|
+
export const iteratorCommandZ = z.enum(IteratorCommand);
|
|
34
|
+
|
|
35
|
+
export enum IteratorResponseVariant {
|
|
36
|
+
None = 0,
|
|
37
|
+
Ack = 1,
|
|
38
|
+
Data = 2,
|
|
39
|
+
}
|
|
40
|
+
export const iteratorResponseVariantZ = z.enum(IteratorResponseVariant);
|
|
@@ -115,6 +115,83 @@ describe("Writer", () => {
|
|
|
115
115
|
expect(true).toBe(true);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
test("auto-index generates server-side timestamps for data-only writes", async () => {
|
|
119
|
+
const [index, data] = await newIndexedPair(client);
|
|
120
|
+
const before = TimeStamp.now();
|
|
121
|
+
const writer = await client.openWriter({
|
|
122
|
+
start: before,
|
|
123
|
+
channels: [data.key],
|
|
124
|
+
autoIndex: true,
|
|
125
|
+
});
|
|
126
|
+
try {
|
|
127
|
+
await writer.write(data.key, randomSeries(5, data.dataType));
|
|
128
|
+
await writer.commit();
|
|
129
|
+
} finally {
|
|
130
|
+
await writer.close();
|
|
131
|
+
}
|
|
132
|
+
const f = await client.read(
|
|
133
|
+
new TimeRange(before, TimeStamp.now().add(TimeSpan.seconds(1))),
|
|
134
|
+
index.key,
|
|
135
|
+
);
|
|
136
|
+
expect(f.length).toEqual(5);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("auto-index mixes user-provided and generated timestamps in one writer", async () => {
|
|
140
|
+
const [index, data] = await newIndexedPair(client);
|
|
141
|
+
const userStamps = secondsLinspace(200, 2);
|
|
142
|
+
const beforeAuto = TimeStamp.now();
|
|
143
|
+
const writer = await client.openWriter({
|
|
144
|
+
start: TimeStamp.seconds(200),
|
|
145
|
+
channels: [index.key, data.key],
|
|
146
|
+
autoIndex: true,
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
await writer.write({
|
|
150
|
+
[index.key]: userStamps,
|
|
151
|
+
[data.key]: randomSeries(2, data.dataType),
|
|
152
|
+
});
|
|
153
|
+
await writer.write(data.key, randomSeries(3, data.dataType));
|
|
154
|
+
await writer.commit();
|
|
155
|
+
} finally {
|
|
156
|
+
await writer.close();
|
|
157
|
+
}
|
|
158
|
+
const userF = await client.read(
|
|
159
|
+
new TimeRange(TimeStamp.seconds(200), TimeStamp.seconds(202)),
|
|
160
|
+
index.key,
|
|
161
|
+
);
|
|
162
|
+
expect(userF.data).toEqual(new BigInt64Array(userStamps.map((v) => v.valueOf())));
|
|
163
|
+
const autoF = await client.read(
|
|
164
|
+
new TimeRange(beforeAuto, TimeStamp.now().add(TimeSpan.seconds(1))),
|
|
165
|
+
index.key,
|
|
166
|
+
);
|
|
167
|
+
expect(autoF.length).toEqual(3);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("auto-index leaves user-provided index data untouched", async () => {
|
|
171
|
+
const [index, data] = await newIndexedPair(client);
|
|
172
|
+
const writer = await client.openWriter({
|
|
173
|
+
start: TimeStamp.seconds(200),
|
|
174
|
+
channels: [index.key, data.key],
|
|
175
|
+
autoIndex: true,
|
|
176
|
+
});
|
|
177
|
+
try {
|
|
178
|
+
await writer.write({
|
|
179
|
+
[index.key]: secondsLinspace(200, 3),
|
|
180
|
+
[data.key]: randomSeries(3, data.dataType),
|
|
181
|
+
});
|
|
182
|
+
await writer.commit();
|
|
183
|
+
} finally {
|
|
184
|
+
await writer.close();
|
|
185
|
+
}
|
|
186
|
+
const f = await client.read(
|
|
187
|
+
new TimeRange(TimeStamp.seconds(200), TimeStamp.seconds(203)),
|
|
188
|
+
index.key,
|
|
189
|
+
);
|
|
190
|
+
expect(f.data).toEqual(
|
|
191
|
+
new BigInt64Array(secondsLinspace(200, 3).map((v) => v.valueOf())),
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
118
195
|
test("write with auto commit and a set interval", async () => {
|
|
119
196
|
const channels = await newIndexedPair(client);
|
|
120
197
|
const writer = await client.openWriter({
|
package/src/framer/writer.ts
CHANGED
|
@@ -60,14 +60,14 @@ const baseWriterConfigZ = z.object({
|
|
|
60
60
|
/** controlSubject sets the control subject of the writer. */
|
|
61
61
|
controlSubject: control.subjectZ.optional(),
|
|
62
62
|
/** authorities set the control authority to set for each channel on the writer.
|
|
63
|
-
* Defaults to absolute authority. If not working with concurrent control,
|
|
64
|
-
*
|
|
63
|
+
* Defaults to absolute authority. If not working with concurrent control, it's best
|
|
64
|
+
* to leave this as the default.
|
|
65
65
|
*/
|
|
66
66
|
authorities: z
|
|
67
67
|
.union([control.authorityZ.transform((a) => [a]), control.authorityZ.array()])
|
|
68
68
|
.default([control.ABSOLUTE_AUTHORITY]),
|
|
69
|
-
/** mode sets the persistence and streaming mode of the writer. The default
|
|
70
|
-
*
|
|
69
|
+
/** mode sets the persistence and streaming mode of the writer. The default mode is
|
|
70
|
+
* WriterModePersistStream.
|
|
71
71
|
*/
|
|
72
72
|
mode: writerModeZ.default(WriterMode.PersistStream),
|
|
73
73
|
/**
|
|
@@ -76,19 +76,23 @@ const baseWriterConfigZ = z.object({
|
|
|
76
76
|
*/
|
|
77
77
|
errOnUnauthorized: z.boolean().default(false),
|
|
78
78
|
/**
|
|
79
|
-
* enableAutoCommit determines whether the writer will automatically commit.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
79
|
+
* enableAutoCommit determines whether the writer will automatically commit. If
|
|
80
|
+
* enableAutoCommit is true, then the writer will commit after each write, and will
|
|
81
|
+
* flush that commit to index after the specified autoIndexPersistInterval.
|
|
82
82
|
*/
|
|
83
83
|
enableAutoCommit: z.boolean().default(true),
|
|
84
84
|
/** autoIndexPersistInterval sets the interval at which commits will be flushed to
|
|
85
85
|
* disk. */
|
|
86
86
|
autoIndexPersistInterval: TimeSpan.z.default(TimeSpan.SECOND),
|
|
87
|
-
|
|
88
|
-
*
|
|
89
|
-
*
|
|
87
|
+
/**
|
|
88
|
+
* autoIndex causes Synnax to automatically generate timestamps for any index channel
|
|
89
|
+
* that is not included in a write call. The first sample in each write is stamped at
|
|
90
|
+
* the time the write is received, and subsequent samples are spaced 1 nanosecond
|
|
91
|
+
* apart. Generated timestamps are guaranteed to be strictly monotonic across all
|
|
92
|
+
* writes on the writer. If the writer is opened with data channels whose index
|
|
93
|
+
* channels are not included, those index channels are added implicitly.
|
|
90
94
|
*/
|
|
91
|
-
|
|
95
|
+
autoIndex: z.boolean().default(false),
|
|
92
96
|
});
|
|
93
97
|
|
|
94
98
|
const netWriterConfigZ = baseWriterConfigZ.extend({
|
|
@@ -212,8 +216,7 @@ export class Writer {
|
|
|
212
216
|
): Promise<Writer> {
|
|
213
217
|
const cfg = zod.parse(writerConfigZ, config);
|
|
214
218
|
const adapter = await WriteAdapter.open(retriever, cfg.channels);
|
|
215
|
-
|
|
216
|
-
client = client.withCodec(new WSWriterCodec(adapter.codec));
|
|
219
|
+
client = client.withCodec(new WSWriterCodec(adapter.codec));
|
|
217
220
|
const stream = await client.stream("/frame/write", reqZ, resZ);
|
|
218
221
|
const writer = new Writer(stream, adapter);
|
|
219
222
|
await writer.execute({
|
|
@@ -245,13 +248,14 @@ export class Writer {
|
|
|
245
248
|
* @param frame - The frame to write to the database. The frame must:
|
|
246
249
|
*
|
|
247
250
|
* 1. Have exactly one array for each key in the list of keys provided to the
|
|
248
|
-
*
|
|
251
|
+
* writer's open method.
|
|
249
252
|
* 2. Have equal length arrays for each key.
|
|
250
253
|
* 3. When writing to an index (i.e. TimeStamp) channel, the values must be
|
|
251
|
-
*
|
|
254
|
+
* monotonically increasing.
|
|
252
255
|
*
|
|
253
|
-
* @
|
|
254
|
-
*
|
|
256
|
+
* @throws if the writer has accumulated an error. Once write throws, all subsequent
|
|
257
|
+
* calls to write and commit will also throw, and the writer must be closed and
|
|
258
|
+
* re-opened to continue writing.
|
|
255
259
|
*/
|
|
256
260
|
async write(
|
|
257
261
|
channelsOrData:
|
|
@@ -263,7 +267,11 @@ export class Writer {
|
|
|
263
267
|
if (this.closeErr != null) throw this.closeErr;
|
|
264
268
|
if (this.stream.received()) return await this.close();
|
|
265
269
|
const frame = await this.adapter.adapt(channelsOrData, series);
|
|
266
|
-
|
|
270
|
+
try {
|
|
271
|
+
this.stream.send({ command: WriterCommand.Write, frame: frame.toPayload() });
|
|
272
|
+
} catch (err) {
|
|
273
|
+
if (!EOF.matches(err)) throw errors.fromUnknown(err);
|
|
274
|
+
}
|
|
267
275
|
}
|
|
268
276
|
|
|
269
277
|
async setAuthority(
|
|
@@ -283,9 +291,9 @@ export class Writer {
|
|
|
283
291
|
* Commits the written frames to the database. Commit is synchronous, meaning that it
|
|
284
292
|
* will not return until all frames have been committed to the database.
|
|
285
293
|
*
|
|
286
|
-
* @returns
|
|
287
|
-
*
|
|
288
|
-
*
|
|
294
|
+
* @returns the timestamp of the last sample written to the writer.
|
|
295
|
+
* @throws if the commit fails or any previous writer method has thrown. Once commit
|
|
296
|
+
* throws, the writer must be closed and re-opened to continue use.
|
|
289
297
|
*/
|
|
290
298
|
async commit(): Promise<TimeStamp> {
|
|
291
299
|
if (this.closeErr != null) throw this.closeErr;
|
|
@@ -315,18 +323,33 @@ export class Writer {
|
|
|
315
323
|
if (WriterClosedError.matches(this.closeErr)) return null;
|
|
316
324
|
throw this.closeErr;
|
|
317
325
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
326
|
+
try {
|
|
327
|
+
const res = await this.stream.receive();
|
|
328
|
+
this.closeErr = errors.decode(res?.err);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
const e = errors.fromUnknown(err);
|
|
331
|
+
this.closeErr = EOF.matches(e) ? new WriterClosedError() : e;
|
|
332
|
+
}
|
|
321
333
|
}
|
|
322
334
|
}
|
|
323
335
|
|
|
324
336
|
private async execute(req: WriteRequest): Promise<Response> {
|
|
325
|
-
|
|
326
|
-
|
|
337
|
+
try {
|
|
338
|
+
this.stream.send(req);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
// A send failure is always EOF or StreamClosed, never WriterClosedError, so
|
|
341
|
+
// closeInternal re-throws here and the receive loop below is reached only when
|
|
342
|
+
// the send succeeds.
|
|
343
|
+
await this.closeInternal(errors.fromUnknown(err));
|
|
344
|
+
}
|
|
327
345
|
while (true) {
|
|
328
|
-
|
|
329
|
-
|
|
346
|
+
let res: Response;
|
|
347
|
+
try {
|
|
348
|
+
res = await this.stream.receive();
|
|
349
|
+
} catch (err) {
|
|
350
|
+
await this.closeInternal(errors.fromUnknown(err));
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
330
353
|
const resErr = errors.decode(res?.err);
|
|
331
354
|
if (resErr != null) await this.closeInternal(resErr);
|
|
332
355
|
if (res?.command == req.command) return res;
|
package/src/group/client.ts
CHANGED
|
@@ -7,7 +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 {
|
|
10
|
+
import { type UnaryClient } from "@synnaxlabs/freighter";
|
|
11
11
|
import { array } from "@synnaxlabs/x";
|
|
12
12
|
import z from "zod";
|
|
13
13
|
|
|
@@ -39,8 +39,7 @@ export class Client {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
async create(args: CreateArgs): Promise<Group> {
|
|
42
|
-
const res = await
|
|
43
|
-
this.client,
|
|
42
|
+
const res = await this.client.send(
|
|
44
43
|
"/ontology/create-group",
|
|
45
44
|
args,
|
|
46
45
|
createReqZ,
|
|
@@ -50,8 +49,7 @@ export class Client {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
async rename(key: Key, name: string): Promise<void> {
|
|
53
|
-
await
|
|
54
|
-
this.client,
|
|
52
|
+
await this.client.send(
|
|
55
53
|
"/ontology/rename-group",
|
|
56
54
|
{ key, name },
|
|
57
55
|
renameReqZ,
|
|
@@ -60,8 +58,7 @@ export class Client {
|
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
async delete(keys: Key | Key[]): Promise<void> {
|
|
63
|
-
await
|
|
64
|
-
this.client,
|
|
61
|
+
await this.client.send(
|
|
65
62
|
"/ontology/delete-group",
|
|
66
63
|
{ keys: array.toArray(keys) },
|
|
67
64
|
deleteReqZ,
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
export { access } from "@/access";
|
|
11
|
+
export { actions } from "@/actions";
|
|
11
12
|
export { arc } from "@/arc";
|
|
12
13
|
export { channel } from "@/channel";
|
|
13
14
|
export { Channel, isCalculated } from "@/channel/client";
|
|
@@ -67,7 +68,7 @@ export {
|
|
|
67
68
|
TimeRange,
|
|
68
69
|
TimeSpan,
|
|
69
70
|
TimeStamp,
|
|
70
|
-
type
|
|
71
|
+
type TimestampFormat,
|
|
72
|
+
type TimeZone,
|
|
71
73
|
type TypedArray,
|
|
72
|
-
type TZInfo,
|
|
73
74
|
} from "@synnaxlabs/x";
|