@synnaxlabs/client 0.1.2 → 0.2.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.
Files changed (170) hide show
  1. package/.DS_Store +0 -0
  2. package/.editorconfig +15 -0
  3. package/.eslintrc.json +33 -0
  4. package/.gitignore +9 -0
  5. package/.nyc_output/20720f2d-6abe-420f-a3c5-304d52d60827.json +1 -0
  6. package/.nyc_output/4725921c-6f1b-4ae9-9819-e455f702d31c.json +1 -0
  7. package/.nyc_output/47478588-5ffd-4332-873c-facaa4a2fc38.json +1 -0
  8. package/.nyc_output/48180641-e0b2-49ab-a6eb-e7910e9eac2f.json +1 -0
  9. package/.nyc_output/cb0abf31-740f-47db-b94a-8e3f8f117cb8.json +1 -0
  10. package/.nyc_output/fc77fce2-dad0-49a8-8d4b-0a9014ecf8c5.json +1 -0
  11. package/.nyc_output/processinfo/20720f2d-6abe-420f-a3c5-304d52d60827.json +1 -0
  12. package/.nyc_output/processinfo/4725921c-6f1b-4ae9-9819-e455f702d31c.json +1 -0
  13. package/.nyc_output/processinfo/47478588-5ffd-4332-873c-facaa4a2fc38.json +1 -0
  14. package/.nyc_output/processinfo/48180641-e0b2-49ab-a6eb-e7910e9eac2f.json +1 -0
  15. package/.nyc_output/processinfo/cb0abf31-740f-47db-b94a-8e3f8f117cb8.json +1 -0
  16. package/.nyc_output/processinfo/fc77fce2-dad0-49a8-8d4b-0a9014ecf8c5.json +1 -0
  17. package/.nyc_output/processinfo/index.json +1 -0
  18. package/.prettierignore +2 -0
  19. package/CHANGELOG.md +5 -0
  20. package/build/main/index.d.ts +1 -1
  21. package/build/main/index.js +2 -3
  22. package/build/main/lib/auth.d.ts +54 -0
  23. package/build/main/lib/auth.js +62 -0
  24. package/build/main/lib/auth.spec.d.ts +1 -0
  25. package/build/main/lib/auth.spec.js +39 -0
  26. package/build/main/lib/channel/channel.spec.js +17 -3
  27. package/build/main/lib/channel/client.d.ts +2 -2
  28. package/build/main/lib/channel/client.js +6 -3
  29. package/build/main/lib/channel/payload.d.ts +2 -2
  30. package/build/main/lib/client.d.ts +13 -6
  31. package/build/main/lib/client.js +16 -4
  32. package/build/main/lib/segment/iterator.spec.js +14 -3
  33. package/build/main/lib/segment/typed.js +4 -4
  34. package/build/main/lib/segment/writer.spec.js +17 -3
  35. package/build/main/lib/telem.d.ts +2 -2
  36. package/build/main/lib/telem.js +4 -4
  37. package/build/main/lib/telem.spec.js +4 -2
  38. package/build/main/lib/transport.d.ts +2 -1
  39. package/build/main/lib/transport.js +5 -1
  40. package/build/main/lib/user/payload.d.ts +12 -0
  41. package/build/main/lib/user/payload.js +9 -0
  42. package/build/main/setupspecs.d.ts +4 -0
  43. package/build/main/setupspecs.js +17 -0
  44. package/build/module/index.d.ts +1 -1
  45. package/build/module/index.js +2 -2
  46. package/build/module/lib/auth.d.ts +54 -0
  47. package/build/module/lib/auth.js +63 -0
  48. package/build/module/lib/auth.spec.d.ts +1 -0
  49. package/build/module/lib/auth.spec.js +34 -0
  50. package/build/module/lib/channel/channel.spec.js +17 -3
  51. package/build/module/lib/channel/client.d.ts +2 -2
  52. package/build/module/lib/channel/client.js +6 -3
  53. package/build/module/lib/channel/payload.d.ts +2 -2
  54. package/build/module/lib/client.d.ts +13 -6
  55. package/build/module/lib/client.js +17 -4
  56. package/build/module/lib/segment/iterator.spec.js +14 -3
  57. package/build/module/lib/segment/typed.js +5 -5
  58. package/build/module/lib/segment/writer.spec.js +18 -4
  59. package/build/module/lib/telem.d.ts +2 -2
  60. package/build/module/lib/telem.js +4 -4
  61. package/build/module/lib/telem.spec.js +5 -3
  62. package/build/module/lib/transport.d.ts +2 -1
  63. package/build/module/lib/transport.js +5 -1
  64. package/build/module/lib/user/payload.d.ts +12 -0
  65. package/build/module/lib/user/payload.js +6 -0
  66. package/build/module/setupspecs.d.ts +4 -0
  67. package/build/module/setupspecs.js +16 -0
  68. package/build/tsconfig.module.tsbuildinfo +1 -0
  69. package/build/tsconfig.tsbuildinfo +1 -0
  70. package/coverage/base.css +224 -0
  71. package/coverage/block-navigation.js +87 -0
  72. package/coverage/favicon.png +0 -0
  73. package/coverage/index.html +191 -0
  74. package/coverage/lcov-report/base.css +224 -0
  75. package/coverage/lcov-report/block-navigation.js +87 -0
  76. package/coverage/lcov-report/favicon.png +0 -0
  77. package/coverage/lcov-report/index.html +191 -0
  78. package/coverage/lcov-report/prettify.css +1 -0
  79. package/coverage/lcov-report/prettify.js +2 -0
  80. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  81. package/coverage/lcov-report/sorter.js +196 -0
  82. package/coverage/lcov-report/src/index.html +116 -0
  83. package/coverage/lcov-report/src/lib/auth.ts.html +340 -0
  84. package/coverage/lcov-report/src/lib/channel/client.ts.html +604 -0
  85. package/coverage/lcov-report/src/lib/channel/creator.ts.html +304 -0
  86. package/coverage/lcov-report/src/lib/channel/index.html +176 -0
  87. package/coverage/lcov-report/src/lib/channel/payload.ts.html +139 -0
  88. package/coverage/lcov-report/src/lib/channel/registry.ts.html +202 -0
  89. package/coverage/lcov-report/src/lib/channel/retriever.ts.html +244 -0
  90. package/coverage/lcov-report/src/lib/client.ts.html +244 -0
  91. package/coverage/lcov-report/src/lib/errors.ts.html +484 -0
  92. package/coverage/lcov-report/src/lib/index.html +176 -0
  93. package/coverage/lcov-report/src/lib/segment/client.ts.html +463 -0
  94. package/coverage/lcov-report/src/lib/segment/index.html +206 -0
  95. package/coverage/lcov-report/src/lib/segment/iterator.ts.html +928 -0
  96. package/coverage/lcov-report/src/lib/segment/payload.ts.html +139 -0
  97. package/coverage/lcov-report/src/lib/segment/splitter.ts.html +181 -0
  98. package/coverage/lcov-report/src/lib/segment/typed.ts.html +307 -0
  99. package/coverage/lcov-report/src/lib/segment/validator.ts.html +331 -0
  100. package/coverage/lcov-report/src/lib/segment/writer.ts.html +727 -0
  101. package/coverage/lcov-report/src/lib/telem.ts.html +2056 -0
  102. package/coverage/lcov-report/src/lib/transport.ts.html +196 -0
  103. package/coverage/lcov-report/src/lib/user/index.html +116 -0
  104. package/coverage/lcov-report/src/lib/user/payload.ts.html +109 -0
  105. package/coverage/lcov-report/src/lib/util/index.html +116 -0
  106. package/coverage/lcov-report/src/lib/util/telem.ts.html +124 -0
  107. package/coverage/lcov-report/src/setupspecs.ts.html +133 -0
  108. package/coverage/lcov.info +1230 -0
  109. package/coverage/prettify.css +1 -0
  110. package/coverage/prettify.js +2 -0
  111. package/coverage/sort-arrow-sprite.png +0 -0
  112. package/coverage/sorter.js +196 -0
  113. package/coverage/src/index.html +116 -0
  114. package/coverage/src/lib/auth.ts.html +340 -0
  115. package/coverage/src/lib/channel/client.ts.html +604 -0
  116. package/coverage/src/lib/channel/creator.ts.html +304 -0
  117. package/coverage/src/lib/channel/index.html +176 -0
  118. package/coverage/src/lib/channel/payload.ts.html +139 -0
  119. package/coverage/src/lib/channel/registry.ts.html +202 -0
  120. package/coverage/src/lib/channel/retriever.ts.html +244 -0
  121. package/coverage/src/lib/client.ts.html +244 -0
  122. package/coverage/src/lib/errors.ts.html +484 -0
  123. package/coverage/src/lib/index.html +176 -0
  124. package/coverage/src/lib/segment/client.ts.html +463 -0
  125. package/coverage/src/lib/segment/index.html +206 -0
  126. package/coverage/src/lib/segment/iterator.ts.html +928 -0
  127. package/coverage/src/lib/segment/payload.ts.html +139 -0
  128. package/coverage/src/lib/segment/splitter.ts.html +181 -0
  129. package/coverage/src/lib/segment/typed.ts.html +307 -0
  130. package/coverage/src/lib/segment/validator.ts.html +331 -0
  131. package/coverage/src/lib/segment/writer.ts.html +727 -0
  132. package/coverage/src/lib/telem.ts.html +2056 -0
  133. package/coverage/src/lib/transport.ts.html +196 -0
  134. package/coverage/src/lib/user/index.html +116 -0
  135. package/coverage/src/lib/user/payload.ts.html +109 -0
  136. package/coverage/src/lib/util/index.html +116 -0
  137. package/coverage/src/lib/util/telem.ts.html +124 -0
  138. package/coverage/src/setupspecs.ts.html +133 -0
  139. package/package.json +2 -2
  140. package/src/index.ts +13 -0
  141. package/src/lib/.DS_Store +0 -0
  142. package/src/lib/auth.spec.ts +36 -0
  143. package/src/lib/auth.ts +85 -0
  144. package/src/lib/channel/channel.spec.ts +49 -0
  145. package/src/lib/channel/client.ts +173 -0
  146. package/src/lib/channel/creator.ts +73 -0
  147. package/src/lib/channel/payload.ts +18 -0
  148. package/src/lib/channel/registry.ts +39 -0
  149. package/src/lib/channel/retriever.ts +53 -0
  150. package/src/lib/client.ts +53 -0
  151. package/src/lib/errors.ts +133 -0
  152. package/src/lib/segment/client.ts +126 -0
  153. package/src/lib/segment/iterator.spec.ts +78 -0
  154. package/src/lib/segment/iterator.ts +281 -0
  155. package/src/lib/segment/payload.ts +18 -0
  156. package/src/lib/segment/splitter.ts +32 -0
  157. package/src/lib/segment/typed.ts +74 -0
  158. package/src/lib/segment/validator.ts +82 -0
  159. package/src/lib/segment/writer.spec.ts +85 -0
  160. package/src/lib/segment/writer.ts +214 -0
  161. package/src/lib/telem.spec.ts +200 -0
  162. package/src/lib/telem.ts +657 -0
  163. package/src/lib/transport.ts +37 -0
  164. package/src/lib/user/payload.ts +8 -0
  165. package/src/lib/util/telem.ts +13 -0
  166. package/src/setupspecs.ts +16 -0
  167. package/tsconfig.json +47 -0
  168. package/tsconfig.module.json +9 -0
  169. package/yarn-error.log +5756 -0
  170. package/yarn.lock +5936 -0
@@ -0,0 +1,281 @@
1
+ import {
2
+ EOF,
3
+ ErrorPayloadSchema,
4
+ Stream,
5
+ StreamClient,
6
+ } from '@synnaxlabs/freighter';
7
+ import { z } from 'zod';
8
+
9
+ import { ChannelPayload } from '../channel/payload';
10
+ import Registry from '../channel/registry';
11
+ import { TimeRange } from '../telem';
12
+
13
+ import { SegmentPayload, SegmentPayloadSchema } from './payload';
14
+ import TypedSegment from './typed';
15
+
16
+ enum Command {
17
+ Open = 0,
18
+ Next = 1,
19
+ Prev = 2,
20
+ First = 3,
21
+ Last = 4,
22
+ NextSpan = 5,
23
+ PrevSpan = 6,
24
+ NextRange = 7,
25
+ Valid = 8,
26
+ Error = 9,
27
+ SeekFirst = 10,
28
+ SeekLast = 11,
29
+ SeekLT = 12,
30
+ SeekGE = 13,
31
+ }
32
+
33
+ enum ResponseVariant {
34
+ None = 0,
35
+ Ack = 1,
36
+ Data = 2,
37
+ }
38
+
39
+ const RequestSchema = z.object({
40
+ command: z.nativeEnum(Command),
41
+ span: z.number().optional(),
42
+ range: z.instanceof(TimeRange).optional(),
43
+ stamp: z.number().optional(),
44
+ keys: z.string().array().optional(),
45
+ });
46
+
47
+ type Request = z.infer<typeof RequestSchema>;
48
+
49
+ const ResponseSchema = z.object({
50
+ variant: z.nativeEnum(ResponseVariant),
51
+ ack: z.boolean(),
52
+ command: z.nativeEnum(Command),
53
+ error: ErrorPayloadSchema.optional(),
54
+ segments: SegmentPayloadSchema.array().nullable(),
55
+ });
56
+
57
+ type Response = z.infer<typeof ResponseSchema>;
58
+
59
+ /**
60
+ * Used to iterate over a clusters telemetry in time-order. It should not be
61
+ * instantiated directly, and should instead be instantiated via the SegmentClient.
62
+ *
63
+ * Using an iterator is ideal when querying/processing large ranges of data, but
64
+ * is relatively complex and difficult to use. If you're looking to retrieve
65
+ * telemetry between two timestamps, see the SegmentClient.read method.
66
+ */
67
+ export class CoreIterator {
68
+ private static ENDPOINT = '/segment/iterate';
69
+ private client: StreamClient;
70
+ private stream: Stream<Request, Response> | undefined;
71
+ private readonly aggregate: boolean = false;
72
+ values: SegmentPayload[] = [];
73
+
74
+ constructor(client: StreamClient, aggregate = false) {
75
+ this.client = client;
76
+ this.aggregate = aggregate;
77
+ }
78
+
79
+ /**
80
+ * Opens the iterator, configuring it to iterate over the telemetry in the
81
+ * channels with the given keys within the provided time range.
82
+ *
83
+ * @param tr - The time range to iterate over.
84
+ * @param keys - The keys of the channels to iterate over.
85
+ */
86
+ async open(tr: TimeRange, keys: string[]) {
87
+ this.stream = await this.client.stream(
88
+ CoreIterator.ENDPOINT,
89
+ RequestSchema,
90
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
91
+ // @ts-ignore
92
+ ResponseSchema
93
+ );
94
+ await this.execute({ command: Command.Open, keys, range: tr });
95
+ this.values = [];
96
+ }
97
+
98
+ /**
99
+ * Reads the next segment for each channel in the iterator.
100
+ *
101
+ * @returns false if the next segment can't be found for one or more channels or
102
+ * the iterator has accumulated an error.
103
+ */
104
+ async next(): Promise<boolean> {
105
+ return this.execute({ command: Command.Next });
106
+ }
107
+
108
+ /**
109
+ * Reads the previous segment for each channel in the iterator.
110
+ *
111
+ * @returns false if the next segment can't be found for one or more channels or
112
+ * the iterator has accumulated an error.
113
+ */
114
+ async prev(): Promise<boolean> {
115
+ return this.execute({ command: Command.Prev });
116
+ }
117
+
118
+ /**
119
+ * Seeks to the beginning of the time range and reads the first segment of each
120
+ * channel in the iterator.
121
+ *
122
+ * @returns false if no segments exists in the time range for a particular channel
123
+ * or the iterator has accumulated an error.
124
+ */
125
+ async first(): Promise<boolean> {
126
+ return this.execute({ command: Command.First });
127
+ }
128
+
129
+ /**
130
+ * Seeks to the end of the time range and reads the last segment of each channel
131
+ * in the iterator.
132
+ *
133
+ * @returns false if no segments exists in the time range for a particular channel,
134
+ * or the iterator has accumulated an error.
135
+ */
136
+ async last(): Promise<boolean> {
137
+ return this.execute({ command: Command.Last });
138
+ }
139
+
140
+ /**
141
+ * Reads the next time span of telemetry for each channel in the iterator.
142
+ *
143
+ * @returns false if a segment satisfying the request can't be found for a
144
+ * particular channel or the iterator has accumulated an error.
145
+ */
146
+ async nextSpan(span: number): Promise<boolean> {
147
+ return this.execute({ command: Command.NextSpan, span });
148
+ }
149
+
150
+ /**
151
+ * Reads the previous time span of telemetry for each channel in the iterator.
152
+ *
153
+ * @returns false if a segment satisfying the request can't be found for a particular
154
+ * channel or the iterator has accumulated an error.
155
+ */
156
+ async prevSpan(span: number): Promise<boolean> {
157
+ return this.execute({ command: Command.PrevSpan, span });
158
+ }
159
+
160
+ /**
161
+ * Seeks the iterator to the start of the time range and reads the telemetry within
162
+ * the range for each channel.
163
+ *
164
+ * @returns: False if a segment satisfying the request can't be found for a particular
165
+ * channel or the iterator has accumulated an error.
166
+ */
167
+ async nextRange(range: TimeRange): Promise<boolean> {
168
+ return this.execute({ command: Command.NextRange, range });
169
+ }
170
+
171
+ /**
172
+ * Seeks the iterator to the first segment in the time range, but does not read
173
+ * it. Also invalidates the iterator. The iterator will not be considered valid
174
+ * until a call to first, last, next, prev, prev_span, next_span, or next_range.
175
+ *
176
+ * @returns false if the iterator is not pointing to a valid segment for a particular
177
+ * channel or has accumulated an error.
178
+ */
179
+ async seekFirst(): Promise<boolean> {
180
+ return this.execute({ command: Command.SeekFirst });
181
+ }
182
+
183
+ /** Seeks the iterator to the last segment in the time range, but does not read it.
184
+ * Also invalidates the iterator. The iterator will not be considered valid
185
+ * until a call to first, last, next, prev, prev_span, next_span, or next_range.
186
+ *
187
+ * @returns false if the iterator is not pointing to a valid segment for a particular
188
+ * channel or has accumulated an error.
189
+ */
190
+ async seekLast(): Promise<boolean> {
191
+ return this.execute({ command: Command.SeekLast });
192
+ }
193
+
194
+ /**
195
+ * Seeks the iterator to the first segment whose start is less than or equal to
196
+ * the provided timestamp. Also invalidates the iterator. The iterator will not be
197
+ * considered valid until a call to first, last, next, prev, prev_span, next_span, or next_range.
198
+ *
199
+ * @returns false if the iterator is not pointing to a valid segment for a particular
200
+ * channel or has accumulated an error.
201
+ */
202
+ async seekLT(stamp: number): Promise<boolean> {
203
+ return this.execute({ command: Command.SeekLT, stamp });
204
+ }
205
+
206
+ /**
207
+ * Seeks the iterator to the first segment whose start is greater than or equal to
208
+ * the provided timestamp. Also invalidates the iterator. The iterator will not be
209
+ * considered valid until a call to first, last, next, prev, prev_span, next_span, or next_range.
210
+ *
211
+ * @returns false if the iterator is not pointing to a valid segment for a particular
212
+ * channel or has accumulated an error.
213
+ */
214
+ async seekGE(stamp: number): Promise<boolean> {
215
+ return this.execute({ command: Command.SeekGE, stamp });
216
+ }
217
+
218
+ /**
219
+ * @returns true if the iterator value contains a valid segment, and fale otherwise.
220
+ * valid most commonly returns false when the iterator is exhausted or has
221
+ * accumulated an error.
222
+ */
223
+ async valid(): Promise<boolean> {
224
+ return this.execute({ command: Command.Valid });
225
+ }
226
+
227
+ /**
228
+ * Closes the iterator. An iterator MUST be closed after use, and this method
229
+ * should probably be placed in a 'finally' block. If the iterator is not closed,
230
+ * it may leak resources.
231
+ */
232
+ async close() {
233
+ if (!this.stream)
234
+ throw new Error('iterator.open() must be called before any other method');
235
+ this.stream.closeSend();
236
+ const [, exc] = await this.stream.receive();
237
+ if (!(exc instanceof EOF)) throw exc;
238
+ }
239
+
240
+ private async execute(request: Request): Promise<boolean> {
241
+ if (!this.stream)
242
+ throw new Error('iterator.open() must be called before any other method');
243
+ const err = this.stream.send(request);
244
+ if (err) throw err;
245
+ if (!this.aggregate) this.values = [];
246
+ for (;;) {
247
+ const [res, err] = await this.stream.receive();
248
+ if (err || !res) throw err;
249
+ if (res.variant == ResponseVariant.Ack) return res.ack;
250
+ if (res.segments) this.values.push(...res.segments);
251
+ }
252
+ }
253
+ }
254
+
255
+ export class TypedIterator extends CoreIterator {
256
+ channels: Registry;
257
+
258
+ constructor(client: StreamClient, channels: Registry, aggregate = false) {
259
+ super(client, aggregate);
260
+ this.channels = channels;
261
+ }
262
+
263
+ async value(): Promise<Record<string, TypedSegment>> {
264
+ const result: Record<string, TypedSegment> = {};
265
+ this.values.sort((a, b) => a.start.valueOf() - b.start.valueOf());
266
+ const keys = this.values.map((v) => v.channelKey);
267
+ const channels = await this.channels.getN(...keys);
268
+ this.values.forEach((v) => {
269
+ const sugared = new TypedSegment(
270
+ channels.find((c) => c.key == v.channelKey) as ChannelPayload,
271
+ v
272
+ );
273
+ if (v.channelKey in result) {
274
+ result[v.channelKey].extend(sugared);
275
+ } else {
276
+ result[v.channelKey] = sugared;
277
+ }
278
+ });
279
+ return result;
280
+ }
281
+ }
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+
3
+ import { TimeStamp } from '../telem';
4
+
5
+ export const SegmentPayloadSchema = z.object({
6
+ channelKey: z.string(),
7
+ start: z.number().transform((n) => new TimeStamp(n)),
8
+ data: z.string().transform(
9
+ (s) =>
10
+ new Uint8Array(
11
+ atob(s)
12
+ .split('')
13
+ .map((c) => c.charCodeAt(0))
14
+ )
15
+ ),
16
+ });
17
+
18
+ export type SegmentPayload = z.infer<typeof SegmentPayloadSchema>;
@@ -0,0 +1,32 @@
1
+ import { ValidationError } from '../errors';
2
+ import { Size } from '../telem';
3
+
4
+ import TypedSegment from './typed';
5
+
6
+ export default class Splitter {
7
+ threshold: Size;
8
+
9
+ constructor(threshold: Size) {
10
+ this.threshold = threshold;
11
+ }
12
+
13
+ split(segment: TypedSegment): TypedSegment[] {
14
+ if (segment.size.smallerThan(this.threshold)) return [segment];
15
+ if (!segment.channel.density)
16
+ throw new ValidationError(
17
+ 'Cannot split segment because channel density is undefined'
18
+ );
19
+ const splitPoint =
20
+ this.threshold.valueOf() -
21
+ (this.threshold.valueOf() % segment.channel.density.valueOf());
22
+ const truncated = new TypedSegment(segment.channel, {
23
+ ...segment.payload,
24
+ data: segment.payload.data.slice(0, splitPoint),
25
+ });
26
+ const next = new TypedSegment(segment.channel, {
27
+ ...segment.payload,
28
+ data: segment.payload.data.slice(splitPoint),
29
+ });
30
+ return [truncated, ...this.split(next)];
31
+ }
32
+ }
@@ -0,0 +1,74 @@
1
+ import { ChannelPayload } from '../channel/payload';
2
+ import { ContiguityError, ValidationError } from '../errors';
3
+ import {
4
+ Density,
5
+ Size,
6
+ TimeRange,
7
+ TimeSpan,
8
+ TimeStamp,
9
+ TypedArray,
10
+ } from '../telem';
11
+
12
+ import { SegmentPayload } from './payload';
13
+
14
+ export default class TypedSegment {
15
+ payload: SegmentPayload;
16
+ channel: ChannelPayload;
17
+ view: TypedArray;
18
+
19
+ constructor(channel: ChannelPayload, payload: SegmentPayload) {
20
+ this.channel = channel;
21
+ this.payload = payload;
22
+ this.view = new this.channel.dataType.arrayConstructor(
23
+ this.payload.data.buffer
24
+ );
25
+ }
26
+
27
+ get start(): TimeStamp {
28
+ return this.payload.start;
29
+ }
30
+
31
+ get span(): TimeSpan {
32
+ return this.channel.rate.byteSpan(
33
+ Size.Bytes(this.view.byteLength),
34
+ this.channel.density as Density
35
+ );
36
+ }
37
+
38
+ get range(): TimeRange {
39
+ return this.start.spanRange(this.span);
40
+ }
41
+
42
+ get end(): TimeStamp {
43
+ return this.range.end;
44
+ }
45
+
46
+ get size(): Size {
47
+ return Size.Bytes(this.view.byteLength);
48
+ }
49
+
50
+ extend(other: TypedSegment) {
51
+ if (other.channel.key !== this.channel.key) {
52
+ throw new ValidationError(`
53
+ Cannot extend segment because channel keys mismatch.
54
+ Segment Channel Key: ${this.channel.key}
55
+ Other Segment Channel Key: ${other.channel.key}
56
+ `);
57
+ } else if (!this.end.equals(other.start)) {
58
+ throw new ContiguityError(`
59
+ Cannot extend segment because segments are not contiguous.
60
+ Segment End: ${this.end}
61
+ Other Segment Start: ${other.start}
62
+ `);
63
+ }
64
+ const newData = new Uint8Array(
65
+ this.view.byteLength + other.view.byteLength
66
+ );
67
+ newData.set(this.payload.data, 0);
68
+ newData.set(other.payload.data, this.view.byteLength);
69
+ this.payload.data = newData;
70
+ this.view = new this.channel.dataType.arrayConstructor(
71
+ this.payload.data.buffer
72
+ );
73
+ }
74
+ }
@@ -0,0 +1,82 @@
1
+ import { ContiguityError, UnexpectedError, ValidationError } from '../errors';
2
+ import { DataType, TimeStamp, TypedArray } from '../telem';
3
+
4
+ import TypedSegment from './typed';
5
+
6
+ export class ScalarTypeValidator {
7
+ validate(array: TypedArray, dataType: DataType): void {
8
+ if (!dataType.checkArray(array)) {
9
+ throw new ValidationError({
10
+ field: 'data',
11
+ message: `Data type mismatch. Expected ${dataType} but got ${array.constructor.name}`,
12
+ });
13
+ }
14
+ }
15
+ }
16
+
17
+ export type ContiguityValidatorProps = {
18
+ allowNoHighWaterMark: boolean;
19
+ allowOverlap: boolean;
20
+ allowGaps: boolean;
21
+ };
22
+
23
+ export class ContiguityValidator {
24
+ highWaterMarks: Map<string, TimeStamp>;
25
+ allowNoHighWaterMark = false;
26
+ allowOverlap = false;
27
+ allowGaps = false;
28
+
29
+ constructor(props: ContiguityValidatorProps) {
30
+ this.highWaterMarks = new Map();
31
+ this.allowNoHighWaterMark = props.allowNoHighWaterMark;
32
+ this.allowOverlap = props.allowOverlap;
33
+ this.allowGaps = props.allowGaps;
34
+ }
35
+
36
+ validate(segment: TypedSegment): void {
37
+ if (!segment.channel.key) {
38
+ throw new UnexpectedError('Channel key is not set');
39
+ }
40
+ const hwm = this.getHighWaterMark(segment.channel.key);
41
+ if (hwm) {
42
+ this.enforceNoOverlap(hwm, segment);
43
+ this.enforceNoGaps(hwm, segment);
44
+ }
45
+ this.updateHighWaterMark(segment);
46
+ }
47
+
48
+ private enforceNoOverlap(hwm: TimeStamp, seg: TypedSegment): void {
49
+ if (!this.allowOverlap && seg.start.before(hwm)) {
50
+ throw new ContiguityError(
51
+ `Segment overlaps with previous segment. Previous segment ends at ${hwm.toString()}
52
+ Segment starts at ${seg.start.toString()}`
53
+ );
54
+ }
55
+ }
56
+
57
+ private enforceNoGaps(hwm: TimeStamp, seg: TypedSegment): void {
58
+ if (!this.allowGaps && !seg.start.equals(hwm)) {
59
+ throw new ContiguityError(
60
+ `Segment is not contiguous with previous segment. Previous segment ends at ${hwm.toString()}
61
+ Segment starts at ${seg.start.toString()}`
62
+ );
63
+ }
64
+ }
65
+
66
+ private getHighWaterMark(key: string): TimeStamp | undefined {
67
+ const hwm = this.highWaterMarks.get(key);
68
+ if (!hwm && !this.allowNoHighWaterMark) {
69
+ throw new UnexpectedError(
70
+ 'No high water mark found for channel key ' + key
71
+ );
72
+ }
73
+ return hwm;
74
+ }
75
+
76
+ private updateHighWaterMark(seg: TypedSegment): void {
77
+ if (!seg.channel.key) {
78
+ throw new UnexpectedError('Channel key is not set');
79
+ }
80
+ this.highWaterMarks.set(seg.channel.key, seg.end);
81
+ }
82
+ }
@@ -0,0 +1,85 @@
1
+ import test from 'ava';
2
+
3
+ import { newClient } from '../../setupspecs';
4
+ import { ContiguityError, ValidationError } from '../errors';
5
+ import { DataType, Rate, Size, TimeSpan } from '../telem';
6
+ import { randomTypedArray } from '../util/telem';
7
+
8
+ const client = newClient();
9
+
10
+ const newChannel = async () => {
11
+ return await client.channel.create({
12
+ name: 'test',
13
+ nodeId: 1,
14
+ rate: Rate.Hz(1),
15
+ dataType: DataType.Float64,
16
+ });
17
+ };
18
+
19
+ test('TypedWriter - basic write', async (t) => {
20
+ const ch = await newChannel();
21
+ const writer = await client.data.newWriter([ch.key]);
22
+ try {
23
+ await writer.write(ch.key, 0, randomTypedArray(10, ch.dataType));
24
+ } finally {
25
+ await writer.close();
26
+ }
27
+ t.true(true);
28
+ });
29
+
30
+ test('TypedWriter - invalid data type', async (t) => {
31
+ const ch = await newChannel();
32
+ const writer = await client.data.newWriter([ch.key]);
33
+ try {
34
+ await writer.write(ch.key, 0, randomTypedArray(16, DataType.Uint8));
35
+ t.fail('Expected error');
36
+ } catch (err) {
37
+ t.true(err instanceof ValidationError);
38
+ } finally {
39
+ await writer.close();
40
+ }
41
+ });
42
+
43
+ test('TypedWriter - non contiguous', async (t) => {
44
+ const ch = await newChannel();
45
+ const writer = await client.data.newWriter([ch.key]);
46
+ try {
47
+ await writer.write(ch.key, 0, randomTypedArray(10, ch.dataType));
48
+ await writer.write(ch.key, 12, randomTypedArray(10, ch.dataType));
49
+ t.fail('Expected error');
50
+ } catch (err) {
51
+ t.true(err instanceof ContiguityError);
52
+ } finally {
53
+ await writer.close();
54
+ }
55
+ });
56
+
57
+ test('TypedWriter - multi segment write', async (t) => {
58
+ const ch = await newChannel();
59
+ const nSamples = 1000;
60
+ const nWrites = 100;
61
+ const writer = await client.data.newWriter([ch.key]);
62
+ const data = randomTypedArray(nSamples, ch.dataType);
63
+ try {
64
+ for (let i = 0; i < nWrites; i++) {
65
+ await writer.write(ch.key, TimeSpan.Seconds(i * nSamples), data);
66
+ }
67
+ } finally {
68
+ await writer.close();
69
+ }
70
+ t.true(true);
71
+ });
72
+
73
+ test('TypedWriter - segment splitting', async (t) => {
74
+ const ch = await newChannel();
75
+ const span = ch.rate.byteSpan(new Size(9e6), ch.density);
76
+ const nSamples = ch.rate.sampleCount(span);
77
+ const data = randomTypedArray(nSamples, ch.dataType);
78
+ const writer = await client.data.newWriter([ch.key]);
79
+ try {
80
+ await writer.write(ch.key, 0, data);
81
+ } finally {
82
+ await writer.close();
83
+ }
84
+ t.true(true);
85
+ });