@synnaxlabs/client 0.41.0 → 0.42.0

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