@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.
Files changed (197) hide show
  1. package/.turbo/turbo-build.log +10 -13
  2. package/dist/client.cjs +60 -36
  3. package/dist/client.js +6435 -4786
  4. package/dist/src/access/policy/client.d.ts +70 -80
  5. package/dist/src/access/policy/client.d.ts.map +1 -1
  6. package/dist/src/access/policy/types.gen.d.ts +18 -20
  7. package/dist/src/access/policy/types.gen.d.ts.map +1 -1
  8. package/dist/src/access/role/client.d.ts.map +1 -1
  9. package/dist/src/access/role/types.gen.d.ts +2 -2
  10. package/dist/src/actions/actions.d.ts +68 -0
  11. package/dist/src/actions/actions.d.ts.map +1 -0
  12. package/dist/src/actions/actions.spec.d.ts +2 -0
  13. package/dist/src/actions/actions.spec.d.ts.map +1 -0
  14. package/dist/src/actions/external.d.ts +2 -0
  15. package/dist/src/actions/external.d.ts.map +1 -0
  16. package/dist/src/actions/index.d.ts +2 -0
  17. package/dist/src/actions/index.d.ts.map +1 -0
  18. package/dist/src/arc/client.d.ts.map +1 -1
  19. package/dist/src/arc/compiler/types.gen.d.ts +1 -1
  20. package/dist/src/arc/compiler/types.gen.d.ts.map +1 -1
  21. package/dist/src/arc/graph/types.gen.d.ts +29 -29
  22. package/dist/src/arc/graph/types.gen.d.ts.map +1 -1
  23. package/dist/src/arc/ir/types.gen.d.ts +123 -123
  24. package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
  25. package/dist/src/arc/module/types.gen.d.ts +45 -45
  26. package/dist/src/arc/program/types.gen.d.ts +45 -45
  27. package/dist/src/arc/types/types.gen.d.ts +11 -11
  28. package/dist/src/arc/types/types.gen.d.ts.map +1 -1
  29. package/dist/src/arc/types.gen.d.ts +99 -99
  30. package/dist/src/auth/auth.d.ts +3 -3
  31. package/dist/src/auth/auth.d.ts.map +1 -1
  32. package/dist/src/channel/client.d.ts +2 -2
  33. package/dist/src/channel/client.d.ts.map +1 -1
  34. package/dist/src/channel/retriever.d.ts +5 -8
  35. package/dist/src/channel/retriever.d.ts.map +1 -1
  36. package/dist/src/channel/types.gen.d.ts +3 -3
  37. package/dist/src/channel/writer.d.ts.map +1 -1
  38. package/dist/src/connection/checker.d.ts +1 -1
  39. package/dist/src/connection/checker.d.ts.map +1 -1
  40. package/dist/src/device/client.d.ts.map +1 -1
  41. package/dist/src/device/types.gen.d.ts +6 -8
  42. package/dist/src/device/types.gen.d.ts.map +1 -1
  43. package/dist/src/errors.d.ts +2 -0
  44. package/dist/src/errors.d.ts.map +1 -1
  45. package/dist/src/framer/adapter.d.ts.map +1 -1
  46. package/dist/src/framer/client.d.ts +2 -2
  47. package/dist/src/framer/codec.d.ts +9 -1
  48. package/dist/src/framer/codec.d.ts.map +1 -1
  49. package/dist/src/framer/deleter.d.ts.map +1 -1
  50. package/dist/src/framer/frame.d.ts +1 -1
  51. package/dist/src/framer/iterator.d.ts +84 -3
  52. package/dist/src/framer/iterator.d.ts.map +1 -1
  53. package/dist/src/framer/streamProxy.d.ts.map +1 -1
  54. package/dist/src/framer/streamer.d.ts +1 -3
  55. package/dist/src/framer/streamer.d.ts.map +1 -1
  56. package/dist/src/framer/types.gen.d.ts +18 -0
  57. package/dist/src/framer/types.gen.d.ts.map +1 -1
  58. package/dist/src/framer/writer.d.ts +8 -8
  59. package/dist/src/framer/writer.d.ts.map +1 -1
  60. package/dist/src/group/client.d.ts +1 -2
  61. package/dist/src/group/client.d.ts.map +1 -1
  62. package/dist/src/group/types.gen.d.ts +2 -2
  63. package/dist/src/index.d.ts +2 -1
  64. package/dist/src/index.d.ts.map +1 -1
  65. package/dist/src/label/client.d.ts +5 -8
  66. package/dist/src/label/client.d.ts.map +1 -1
  67. package/dist/src/lineplot/client.d.ts.map +1 -1
  68. package/dist/src/lineplot/types.gen.d.ts +2 -2
  69. package/dist/src/log/client.d.ts.map +1 -1
  70. package/dist/src/log/types.gen.d.ts +2 -2
  71. package/dist/src/ontology/client.d.ts +1 -3
  72. package/dist/src/ontology/client.d.ts.map +1 -1
  73. package/dist/src/ontology/payload.d.ts +12 -16
  74. package/dist/src/ontology/payload.d.ts.map +1 -1
  75. package/dist/src/ontology/types.gen.d.ts +1 -2
  76. package/dist/src/ontology/types.gen.d.ts.map +1 -1
  77. package/dist/src/ontology/writer.d.ts +5 -10
  78. package/dist/src/ontology/writer.d.ts.map +1 -1
  79. package/dist/src/rack/client.d.ts.map +1 -1
  80. package/dist/src/rack/types.gen.d.ts +3 -3
  81. package/dist/src/ranger/alias/client.d.ts.map +1 -1
  82. package/dist/src/ranger/client.d.ts.map +1 -1
  83. package/dist/src/ranger/kv/client.d.ts.map +1 -1
  84. package/dist/src/ranger/types.gen.d.ts +6 -6
  85. package/dist/src/ranger/types.gen.d.ts.map +1 -1
  86. package/dist/src/ranger/writer.d.ts +2 -3
  87. package/dist/src/ranger/writer.d.ts.map +1 -1
  88. package/dist/src/schematic/actions.d.ts +147 -0
  89. package/dist/src/schematic/actions.d.ts.map +1 -0
  90. package/dist/src/schematic/actions.gen.d.ts +484 -0
  91. package/dist/src/schematic/actions.gen.d.ts.map +1 -0
  92. package/dist/src/schematic/actions.spec.d.ts +2 -0
  93. package/dist/src/schematic/actions.spec.d.ts.map +1 -0
  94. package/dist/src/schematic/client.d.ts +53 -2
  95. package/dist/src/schematic/client.d.ts.map +1 -1
  96. package/dist/src/schematic/external.d.ts +2 -0
  97. package/dist/src/schematic/external.d.ts.map +1 -1
  98. package/dist/src/schematic/symbol/client.d.ts.map +1 -1
  99. package/dist/src/schematic/symbol/types.gen.d.ts +48 -58
  100. package/dist/src/schematic/symbol/types.gen.d.ts.map +1 -1
  101. package/dist/src/schematic/types.gen.d.ts +131 -5
  102. package/dist/src/schematic/types.gen.d.ts.map +1 -1
  103. package/dist/src/status/client.d.ts.map +1 -1
  104. package/dist/src/status/payload.d.ts +3 -3
  105. package/dist/src/table/actions.d.ts +156 -0
  106. package/dist/src/table/actions.d.ts.map +1 -0
  107. package/dist/src/table/actions.gen.d.ts +587 -0
  108. package/dist/src/table/actions.gen.d.ts.map +1 -0
  109. package/dist/src/table/client.d.ts +28 -2
  110. package/dist/src/table/client.d.ts.map +1 -1
  111. package/dist/src/table/external.d.ts +2 -0
  112. package/dist/src/table/external.d.ts.map +1 -1
  113. package/dist/src/table/types.gen.d.ts +71 -4
  114. package/dist/src/table/types.gen.d.ts.map +1 -1
  115. package/dist/src/task/client.d.ts.map +1 -1
  116. package/dist/src/task/types.gen.d.ts +7 -7
  117. package/dist/src/task/types.gen.d.ts.map +1 -1
  118. package/dist/src/user/client.d.ts +2 -2
  119. package/dist/src/user/client.d.ts.map +1 -1
  120. package/dist/src/user/types.gen.d.ts +2 -2
  121. package/dist/src/view/client.d.ts.map +1 -1
  122. package/dist/src/view/types.gen.d.ts +2 -2
  123. package/dist/src/workspace/client.d.ts.map +1 -1
  124. package/dist/src/workspace/types.gen.d.ts +3 -3
  125. package/dist/src/workspace/types.gen.d.ts.map +1 -1
  126. package/package.json +12 -11
  127. package/src/access/policy/client.ts +4 -7
  128. package/src/access/role/client.ts +6 -26
  129. package/src/actions/actions.spec.ts +229 -0
  130. package/src/actions/actions.ts +104 -0
  131. package/src/actions/external.ts +10 -0
  132. package/src/actions/index.ts +10 -0
  133. package/src/arc/client.ts +3 -7
  134. package/src/arc/compiler/types.gen.ts +2 -1
  135. package/src/arc/ir/types.gen.ts +2 -2
  136. package/src/arc/lsp.spec.ts +3 -7
  137. package/src/arc/types/types.gen.ts +3 -3
  138. package/src/auth/auth.spec.ts +12 -13
  139. package/src/auth/auth.ts +36 -34
  140. package/src/channel/batchRetriever.spec.ts +13 -4
  141. package/src/channel/client.ts +8 -6
  142. package/src/channel/retriever.ts +7 -16
  143. package/src/channel/writer.ts +4 -20
  144. package/src/connection/checker.ts +6 -6
  145. package/src/connection/connection.spec.ts +5 -8
  146. package/src/device/client.ts +5 -8
  147. package/src/device/types.gen.ts +4 -4
  148. package/src/errors.ts +9 -9
  149. package/src/framer/adapter.ts +2 -4
  150. package/src/framer/client.ts +1 -1
  151. package/src/framer/codec.spec.ts +53 -3
  152. package/src/framer/codec.ts +58 -25
  153. package/src/framer/deleter.ts +2 -8
  154. package/src/framer/iterator.ts +43 -40
  155. package/src/framer/streamProxy.ts +13 -13
  156. package/src/framer/streamer.spec.ts +12 -3
  157. package/src/framer/streamer.ts +7 -12
  158. package/src/framer/types.gen.ts +20 -0
  159. package/src/framer/writer.spec.ts +77 -0
  160. package/src/framer/writer.ts +51 -28
  161. package/src/group/client.ts +4 -7
  162. package/src/index.ts +3 -2
  163. package/src/label/client.ts +6 -16
  164. package/src/lineplot/client.ts +6 -21
  165. package/src/log/client.ts +6 -21
  166. package/src/ontology/client.ts +3 -4
  167. package/src/ontology/types.gen.ts +0 -1
  168. package/src/ontology/writer.ts +4 -7
  169. package/src/rack/client.ts +4 -7
  170. package/src/ranger/alias/client.ts +6 -11
  171. package/src/ranger/client.ts +3 -4
  172. package/src/ranger/kv/client.ts +5 -8
  173. package/src/ranger/writer.ts +4 -17
  174. package/src/schematic/access.spec.ts +6 -6
  175. package/src/schematic/actions.gen.ts +200 -0
  176. package/src/schematic/actions.spec.ts +699 -0
  177. package/src/schematic/actions.ts +168 -0
  178. package/src/schematic/client.ts +34 -30
  179. package/src/schematic/external.ts +2 -0
  180. package/src/schematic/schematic.spec.ts +233 -69
  181. package/src/schematic/symbol/client.ts +6 -11
  182. package/src/schematic/symbol/types.gen.ts +1 -10
  183. package/src/schematic/types.gen.ts +55 -6
  184. package/src/status/client.ts +4 -10
  185. package/src/table/access.spec.ts +0 -6
  186. package/src/table/actions.gen.ts +243 -0
  187. package/src/table/actions.ts +255 -0
  188. package/src/table/client.ts +21 -25
  189. package/src/table/external.ts +2 -0
  190. package/src/table/table.spec.ts +588 -43
  191. package/src/table/types.gen.ts +58 -5
  192. package/src/task/client.ts +7 -11
  193. package/src/task/types.gen.ts +8 -6
  194. package/src/user/client.ts +6 -11
  195. package/src/view/client.ts +4 -7
  196. package/src/workspace/client.ts +6 -16
  197. package/src/workspace/types.gen.ts +2 -1
@@ -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 StreamClient } from "@synnaxlabs/freighter";
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
- enum Command {
30
- Open = 0,
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
- const resZ = z.object({
59
- variant: z.enum(ResponseVariant),
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(Command),
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 reqZ, typeof resZ>;
74
+ private readonly stream: StreamProxy<typeof iteratorReqZ, typeof iteratorResZ>;
88
75
  private readonly adapter: ReadAdapter;
89
76
  value: Frame;
90
77
 
91
- private constructor(stream: Stream<typeof reqZ, typeof resZ>, adapter: ReadAdapter) {
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: StreamClient,
102
+ client: WebSocketClient,
113
103
  opts: IteratorConfig = {},
114
104
  ): Promise<Iterator> {
115
105
  const adapter = await ReadAdapter.open(retriever, channels);
116
- const stream = await client.stream("/frame/iterate", reqZ, resZ);
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: Command.Open,
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({ command: Command.Next, span: new TimeSpan(span) });
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({ command: Command.Prev, span: new TimeSpan(span) });
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: Command.SeekFirst });
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: Command.SeekLast });
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({ command: Command.SeekLE, stamp: new TimeStamp(stamp) });
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({ command: Command.SeekGE, stamp: new TimeStamp(stamp) });
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: Command.Valid });
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: Request): Promise<boolean> {
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 === ResponseVariant.Ack) return res.ack;
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
- const [res, err] = await this.stream.receive();
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
- const [res, err] = await this.stream.receive();
36
- if (res != null)
37
- console.warn(
38
- `${this.name} received unexpected response on ${JSON.stringify(res)} closure.
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
- const err = this.stream.send(req);
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 { DataType, id, Rate, Series, sleep, TimeSpan, TimeStamp } from "@synnaxlabs/x";
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], useHighPerformanceCodec: true };
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) => {
@@ -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
- if (cfg.useHighPerformanceCodec)
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
- const [, err] = await stream.receive();
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
  }
@@ -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({
@@ -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
- * it's best to leave this as the default.
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
- * mode is WriterModePersistStream.
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
- * If enableAutoCommit is true, then the writer will commit after each write, and
81
- * will flush that commit to index after the specified autoIndexPersistInterval.
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
- * useHighPerformanceCodec sets whether the writer will use the synnax frame
89
- * encoder as opposed to the standard JSON encoding mechanisms for frames.
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
- useHighPerformanceCodec: z.boolean().default(true),
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
- if (cfg.useHighPerformanceCodec)
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
- * writer's open method.
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
- * monotonically increasing.
254
+ * monotonically increasing.
252
255
  *
253
- * @returns false if the writer has accumulated an error. In this case, the caller
254
- * should acknowledge the error by calling the error method or closing the writer.
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
- this.stream.send({ command: WriterCommand.Write, frame: frame.toPayload() });
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 false if the commit failed due to an error. In this case, the caller
287
- * should acknowledge the error by calling the error method or closing the writer.
288
- * After the caller acknowledges the error, they can attempt to commit again.
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
- const [res, err] = await this.stream.receive();
319
- if (err != null) this.closeErr = EOF.matches(err) ? new WriterClosedError() : err;
320
- else this.closeErr = errors.decode(res?.err);
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
- const err = this.stream.send(req);
326
- if (err != null) await this.closeInternal(err);
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
- const [res, err] = await this.stream.receive();
329
- if (err != null) await this.closeInternal(err);
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;
@@ -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 { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
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 sendRequired(
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 sendRequired(
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 sendRequired(
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 TimeStampStringFormat,
71
+ type TimestampFormat,
72
+ type TimeZone,
71
73
  type TypedArray,
72
- type TZInfo,
73
74
  } from "@synnaxlabs/x";