@synnaxlabs/x 0.41.0 → 0.42.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 (159) hide show
  1. package/.turbo/turbo-build.log +23 -23
  2. package/dist/binary.cjs +1 -1
  3. package/dist/binary.js +2 -2
  4. package/dist/{bounds-M-SZ3X1Z.cjs → bounds-BQo7rvs9.cjs} +1 -1
  5. package/dist/{bounds-DQrjn60Q.js → bounds-Bn5_l4Z3.js} +10 -9
  6. package/dist/bounds.cjs +1 -1
  7. package/dist/bounds.js +1 -1
  8. package/dist/compare.cjs +1 -1
  9. package/dist/compare.js +1 -1
  10. package/dist/deep.cjs +1 -1
  11. package/dist/deep.js +84 -77
  12. package/dist/{dimensions-PWy5QZoM.cjs → dimensions-D2QGoNXO.cjs} +1 -1
  13. package/dist/dimensions.cjs +1 -1
  14. package/dist/{external-CvWr1nhS.cjs → external-DWQITF5_.cjs} +1 -1
  15. package/dist/index-BywOGO8U.js +1074 -0
  16. package/dist/index-CYYjI7Uf.cjs +1 -0
  17. package/dist/index-C_6NXBlg.cjs +3 -0
  18. package/dist/{index-BVC_8Cg9.js → index-QGplUHuy.js} +1 -1
  19. package/dist/index.cjs +3 -3
  20. package/dist/index.js +702 -243
  21. package/dist/record.js +3 -1
  22. package/dist/{scale-DL9VFGhL.cjs → scale-BtZINJ-A.cjs} +1 -1
  23. package/dist/{scale-DQwBWnwc.js → scale-DfJe9755.js} +1 -1
  24. package/dist/scale.cjs +1 -1
  25. package/dist/scale.js +1 -1
  26. package/dist/{series-D0zxMWxP.js → series-B9JERcqi.js} +571 -492
  27. package/dist/series-DqJ6f97G.cjs +11 -0
  28. package/dist/spatial.cjs +1 -1
  29. package/dist/spatial.js +2 -2
  30. package/dist/src/binary/{encoder.d.ts → codec.d.ts} +14 -8
  31. package/dist/src/binary/codec.d.ts.map +1 -0
  32. package/dist/src/binary/codec.spec.d.ts +2 -0
  33. package/dist/src/binary/codec.spec.d.ts.map +1 -0
  34. package/dist/src/binary/index.d.ts +1 -1
  35. package/dist/src/binary/index.d.ts.map +1 -1
  36. package/dist/src/breaker/breaker.d.ts +14 -21
  37. package/dist/src/breaker/breaker.d.ts.map +1 -1
  38. package/dist/src/change/change.d.ts +5 -18
  39. package/dist/src/change/change.d.ts.map +1 -1
  40. package/dist/src/color/color.d.ts +126 -0
  41. package/dist/src/color/color.d.ts.map +1 -0
  42. package/dist/src/color/color.spec.d.ts +2 -0
  43. package/dist/src/color/color.spec.d.ts.map +1 -0
  44. package/dist/src/color/external.d.ts +5 -0
  45. package/dist/src/color/external.d.ts.map +1 -0
  46. package/dist/src/color/gradient.d.ts +18 -0
  47. package/dist/src/color/gradient.d.ts.map +1 -0
  48. package/dist/src/color/index.d.ts +2 -0
  49. package/dist/src/color/index.d.ts.map +1 -0
  50. package/dist/src/color/palette.d.ts +19 -0
  51. package/dist/src/color/palette.d.ts.map +1 -0
  52. package/dist/src/color/transformColorsToHex.d.ts +6 -0
  53. package/dist/src/color/transformColorsToHex.d.ts.map +1 -0
  54. package/dist/src/control/control.d.ts +69 -74
  55. package/dist/src/control/control.d.ts.map +1 -1
  56. package/dist/src/deep/merge.d.ts +1 -1
  57. package/dist/src/deep/merge.d.ts.map +1 -1
  58. package/dist/src/errors/errors.d.ts +127 -7
  59. package/dist/src/errors/errors.d.ts.map +1 -1
  60. package/dist/src/errors/errors.spec.d.ts +2 -0
  61. package/dist/src/errors/errors.spec.d.ts.map +1 -0
  62. package/dist/src/index.d.ts +4 -0
  63. package/dist/src/index.d.ts.map +1 -1
  64. package/dist/src/jsonrpc/jsonrpc.d.ts +10 -7
  65. package/dist/src/jsonrpc/jsonrpc.d.ts.map +1 -1
  66. package/dist/src/kv/types.d.ts +1 -7
  67. package/dist/src/kv/types.d.ts.map +1 -1
  68. package/dist/src/migrate/migrate.d.ts +1 -1
  69. package/dist/src/notation/notation.d.ts +5 -1
  70. package/dist/src/notation/notation.d.ts.map +1 -1
  71. package/dist/src/record.d.ts +2 -1
  72. package/dist/src/record.d.ts.map +1 -1
  73. package/dist/src/replace.d.ts +2 -0
  74. package/dist/src/replace.d.ts.map +1 -0
  75. package/dist/src/runtime/os.d.ts +9 -1
  76. package/dist/src/runtime/os.d.ts.map +1 -1
  77. package/dist/src/singleton/define.d.ts +9 -0
  78. package/dist/src/singleton/define.d.ts.map +1 -0
  79. package/dist/src/singleton/define.spec.d.ts +2 -0
  80. package/dist/src/singleton/define.spec.d.ts.map +1 -0
  81. package/dist/src/singleton/index.d.ts +2 -0
  82. package/dist/src/singleton/index.d.ts.map +1 -0
  83. package/dist/src/spatial/base.d.ts +74 -70
  84. package/dist/src/spatial/base.d.ts.map +1 -1
  85. package/dist/src/spatial/box/box.d.ts +18 -76
  86. package/dist/src/spatial/box/box.d.ts.map +1 -1
  87. package/dist/src/spatial/dimensions/dimensions.d.ts +5 -29
  88. package/dist/src/spatial/dimensions/dimensions.d.ts.map +1 -1
  89. package/dist/src/spatial/direction/direction.d.ts +9 -1
  90. package/dist/src/spatial/direction/direction.d.ts.map +1 -1
  91. package/dist/src/spatial/location/location.d.ts +43 -22
  92. package/dist/src/spatial/location/location.d.ts.map +1 -1
  93. package/dist/src/spatial/scale/scale.d.ts +12 -120
  94. package/dist/src/spatial/scale/scale.d.ts.map +1 -1
  95. package/dist/src/spatial/xy/xy.d.ts +5 -29
  96. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  97. package/dist/src/sync/index.d.ts +2 -0
  98. package/dist/src/sync/index.d.ts.map +1 -0
  99. package/dist/src/sync/mutex.d.ts +8 -0
  100. package/dist/src/sync/mutex.d.ts.map +1 -0
  101. package/dist/src/telem/gl.d.ts +4 -1
  102. package/dist/src/telem/gl.d.ts.map +1 -1
  103. package/dist/src/telem/series.d.ts +46 -125
  104. package/dist/src/telem/series.d.ts.map +1 -1
  105. package/dist/src/telem/telem.d.ts +101 -86
  106. package/dist/src/telem/telem.d.ts.map +1 -1
  107. package/dist/src/toArray.d.ts +1 -1
  108. package/dist/src/toArray.d.ts.map +1 -1
  109. package/dist/src/zod/util.d.ts.map +1 -1
  110. package/dist/telem.cjs +1 -1
  111. package/dist/telem.js +1 -1
  112. package/dist/toArray.cjs +1 -1
  113. package/dist/toArray.js +1 -1
  114. package/dist/zod.cjs +1 -1
  115. package/package.json +5 -2
  116. package/src/binary/codec.spec.ts +370 -0
  117. package/src/binary/{encoder.ts → codec.ts} +55 -11
  118. package/src/binary/index.ts +1 -1
  119. package/src/breaker/breaker.spec.ts +16 -25
  120. package/src/breaker/breaker.ts +36 -19
  121. package/src/color/color.spec.ts +673 -0
  122. package/src/color/color.ts +317 -0
  123. package/src/color/external.ts +13 -0
  124. package/src/color/gradient.ts +78 -0
  125. package/src/color/index.ts +10 -0
  126. package/src/color/palette.ts +28 -0
  127. package/src/color/transformColorsToHex.ts +30 -0
  128. package/src/control/control.ts +30 -22
  129. package/src/deep/merge.spec.ts +60 -0
  130. package/src/deep/merge.ts +13 -8
  131. package/src/errors/errors.spec.ts +152 -0
  132. package/src/errors/errors.ts +225 -10
  133. package/src/index.ts +4 -0
  134. package/src/jsonrpc/jsonrpc.ts +12 -8
  135. package/src/migrate/migrate.ts +2 -2
  136. package/src/primitive.ts +1 -1
  137. package/src/record.ts +5 -1
  138. package/src/replace.ts +1 -0
  139. package/src/singleton/define.spec.ts +93 -0
  140. package/src/singleton/define.ts +27 -0
  141. package/src/singleton/index.ts +10 -0
  142. package/src/sync/index.ts +1 -0
  143. package/src/sync/mutex.ts +16 -0
  144. package/src/telem/series.spec.ts +32 -0
  145. package/src/telem/series.ts +54 -19
  146. package/src/telem/telem.spec.ts +151 -10
  147. package/src/telem/telem.ts +126 -73
  148. package/src/toArray.ts +2 -2
  149. package/src/zod/util.spec.ts +17 -1
  150. package/src/zod/util.ts +4 -2
  151. package/tsconfig.tsbuildinfo +1 -1
  152. package/dist/index-BG3Scw3G.cjs +0 -1
  153. package/dist/index-C3QzbIwt.js +0 -101
  154. package/dist/index-CnclyYpG.cjs +0 -3
  155. package/dist/series-BMma2b5q.cjs +0 -11
  156. package/dist/src/binary/encoder.d.ts.map +0 -1
  157. package/dist/src/binary/encoder.spec.d.ts +0 -2
  158. package/dist/src/binary/encoder.spec.d.ts.map +0 -1
  159. package/src/binary/encoder.spec.ts +0 -174
@@ -0,0 +1,370 @@
1
+ // Copyright 2025 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import { describe, expect, it } from "vitest";
11
+ import { z } from "zod";
12
+
13
+ import { binary } from "@/binary";
14
+ import { JSON_CODEC, MSGPACK_CODEC } from "@/binary/codec";
15
+
16
+ const sampleSchema = z.object({
17
+ channelKey: z.string(),
18
+ timeStamp: z.number(),
19
+ value: z.unknown(),
20
+ });
21
+
22
+ [JSON_CODEC, MSGPACK_CODEC].forEach((codec) => {
23
+ describe(`encoder ${codec.contentType}`, () => {
24
+ it("should correctly encode and decode items", () => {
25
+ const sample = {
26
+ channelKey: "test",
27
+ timeStamp: 123,
28
+ value: [1, 2, 3],
29
+ };
30
+ const encoded = codec.encode(sample);
31
+ expect(codec.decode(encoded, sampleSchema)).toEqual(sample);
32
+ });
33
+ });
34
+ });
35
+
36
+ describe("JSON", () => {
37
+ it("should correctly convert keys to snake case", () => {
38
+ const sample = {
39
+ channelKey: "test",
40
+ timeStamp: 123,
41
+ value: new Array([1, 2, 3]),
42
+ };
43
+ const encoded = binary.JSON_CODEC.encodeString(sample);
44
+ const parse = JSON.parse(encoded);
45
+ expect(parse.channel_key).toEqual("test");
46
+ });
47
+ it("should correctly decode keys from snake case", () => {
48
+ const sample = {
49
+ channel_key: "test",
50
+ time_stamp: 123,
51
+ value: new Array([1, 2, 3]),
52
+ };
53
+ const encoded = JSON.stringify(sample);
54
+ const decoded = binary.JSON_CODEC.decodeString(encoded, sampleSchema);
55
+ expect(decoded.channelKey).toEqual("test");
56
+ });
57
+ });
58
+
59
+ describe("CSV", () => {
60
+ it("should correctly decode CSV data with valid input", () => {
61
+ const sample = `
62
+ channelKey,timeStamp,value
63
+ test,123,5
64
+ test2,124,6
65
+ `;
66
+ const decoded = binary.CSV_CODEC.decodeString(sample);
67
+ expect(decoded).toEqual({
68
+ channelKey: ["test", "test2"],
69
+ timeStamp: [123, 124],
70
+ value: [5, 6],
71
+ });
72
+ });
73
+
74
+ it("should handle empty CSV data", () => {
75
+ const sample = `
76
+ `;
77
+ const decoded = binary.CSV_CODEC.decodeString(sample);
78
+ expect(decoded).toEqual({});
79
+ });
80
+
81
+ it("should handle CSV with only headers", () => {
82
+ const sample = `
83
+ channelKey,timeStamp,value
84
+ `;
85
+ const decoded = binary.CSV_CODEC.decodeString(sample);
86
+ expect(decoded).toEqual({
87
+ channelKey: [],
88
+ timeStamp: [],
89
+ value: [],
90
+ });
91
+ });
92
+
93
+ it("should handle CSV with missing values", () => {
94
+ const sample = `
95
+ channelKey,timeStamp,value
96
+ test,123,
97
+ test2,124,6
98
+ `;
99
+ const decoded = binary.CSV_CODEC.decodeString(sample);
100
+ expect(decoded).toEqual({
101
+ channelKey: ["test", "test2"],
102
+ timeStamp: [123, 124],
103
+ value: [6],
104
+ });
105
+ });
106
+
107
+ it("should handle CSV with extra values", () => {
108
+ const sample = `
109
+ channelKey,timeStamp,value
110
+ test,123,5,extra
111
+ test2,124,6
112
+ `;
113
+ const decoded = binary.CSV_CODEC.decodeString(sample);
114
+ expect(decoded).toEqual({
115
+ channelKey: ["test", "test2"],
116
+ timeStamp: [123, 124],
117
+ value: [5, 6],
118
+ });
119
+ });
120
+
121
+ it("should handle CSV with different types of values", () => {
122
+ const sample = `
123
+ key,number,string
124
+ test,123,"hello"
125
+ test2,456,"world"
126
+ `;
127
+ const decoded = binary.CSV_CODEC.decodeString(sample);
128
+ expect(decoded).toEqual({
129
+ key: ["test", "test2"],
130
+ number: [123, 456],
131
+ string: ["hello", "world"],
132
+ });
133
+ });
134
+
135
+ it("should handle CSV with spaces around values", () => {
136
+ const sample = `
137
+ key, number , string
138
+ test , 123 , "hello"
139
+ test2 , 456 , "world"
140
+ `;
141
+ const decoded = binary.CSV_CODEC.decodeString(sample);
142
+ expect(decoded).toEqual({
143
+ key: ["test", "test2"],
144
+ number: [123, 456],
145
+ string: ["hello", "world"],
146
+ });
147
+ });
148
+
149
+ it("should handle CSV with empty rows", () => {
150
+ const sample = `
151
+ key,number,string
152
+ test,123,"hello"
153
+ ,
154
+ test2,456,"world"
155
+ `;
156
+ const decoded = binary.CSV_CODEC.decodeString(sample);
157
+ expect(decoded).toEqual({
158
+ key: ["test", "test2"],
159
+ number: [123, 456],
160
+ string: ["hello", "world"],
161
+ });
162
+ });
163
+
164
+ it("should handle CSV with single column", () => {
165
+ const sample = `
166
+ key
167
+ test
168
+ test2
169
+ `;
170
+ const decoded = binary.CSV_CODEC.decodeString(sample);
171
+ expect(decoded).toEqual({
172
+ key: ["test", "test2"],
173
+ });
174
+ });
175
+
176
+ it("should correctly encode array of objects to CSV", () => {
177
+ const sampleData = [
178
+ { name: "John", age: 30, city: "New York" },
179
+ { name: "Alice", age: 25, city: "Boston" },
180
+ { name: "Bob", age: 40, city: "Chicago" },
181
+ ];
182
+
183
+ const encoded = binary.CSV_CODEC.encodeString(sampleData);
184
+ expect(encoded).toBe(
185
+ 'name,age,city\n"John",30,"New York"\n"Alice",25,"Boston"\n"Bob",40,"Chicago"',
186
+ );
187
+ });
188
+
189
+ it("should handle objects with missing values", () => {
190
+ const sampleData = [
191
+ { name: "John", age: 30, city: "New York" },
192
+ { name: "Alice", age: 25 },
193
+ { name: "Bob", city: "Chicago" },
194
+ ];
195
+
196
+ const encoded = binary.CSV_CODEC.encodeString(sampleData);
197
+ expect(encoded).toBe(
198
+ 'name,age,city\n"John",30,"New York"\n"Alice",25,""\n"Bob","","Chicago"',
199
+ );
200
+ });
201
+
202
+ it("should handle objects with null and undefined values", () => {
203
+ const sampleData = [
204
+ { name: "John", age: null, city: undefined },
205
+ { name: "Alice", age: 25, city: null },
206
+ ];
207
+
208
+ const encoded = binary.CSV_CODEC.encodeString(sampleData);
209
+ expect(encoded).toBe('name,age,city\n"John","",""\n"Alice",25,""');
210
+ });
211
+
212
+ it("should handle different data types in objects", () => {
213
+ const sampleData = [
214
+ { name: "John", active: true, score: 98.5 },
215
+ { name: "Alice", active: false, score: 92.3 },
216
+ ];
217
+
218
+ const encoded = binary.CSV_CODEC.encodeString(sampleData);
219
+ expect(encoded).toBe('name,active,score\n"John",true,98.5\n"Alice",false,92.3');
220
+ });
221
+
222
+ it("should throw error when encoding empty array", () => {
223
+ const sampleData: any[] = [];
224
+
225
+ expect(() => {
226
+ binary.CSV_CODEC.encodeString(sampleData);
227
+ }).toThrow("Payload must be an array of objects");
228
+ });
229
+
230
+ it("should throw error when encoding non-array data", () => {
231
+ const sampleData = { name: "John", age: 30 };
232
+
233
+ expect(() => {
234
+ binary.CSV_CODEC.encodeString(sampleData);
235
+ }).toThrow("Payload must be an array of objects");
236
+ });
237
+
238
+ it("should round-trip encode and decode CSV data", () => {
239
+ const sampleData = [
240
+ { name: "John", age: 30, city: "New York" },
241
+ { name: "Alice", age: 25, city: "Boston" },
242
+ ];
243
+
244
+ const encoded = binary.CSV_CODEC.encode(sampleData);
245
+ const decoded = binary.CSV_CODEC.decode(encoded);
246
+
247
+ expect(decoded).toEqual({
248
+ name: ["John", "Alice"],
249
+ age: [30, 25],
250
+ city: ["New York", "Boston"],
251
+ });
252
+ });
253
+ });
254
+
255
+ describe("MsgPack", () => {
256
+ it("should correctly convert keys to snake case", () => {
257
+ const sample = {
258
+ channelKey: "test",
259
+ timeStamp: 123,
260
+ value: [1, 2, 3],
261
+ };
262
+ const encoded = binary.MSGPACK_CODEC.encode(sample);
263
+ const decoded = binary.MSGPACK_CODEC.decode(encoded);
264
+ expect(decoded).toEqual(sample);
265
+ });
266
+
267
+ it("should correctly validate with schema", () => {
268
+ const sample = {
269
+ channelKey: "test",
270
+ timeStamp: 123,
271
+ value: [1, 2, 3],
272
+ };
273
+ const encoded = binary.MSGPACK_CODEC.encode(sample);
274
+ const decoded = binary.MSGPACK_CODEC.decode(encoded, sampleSchema);
275
+ expect(decoded).toEqual(sample);
276
+ });
277
+
278
+ it("should handle complex nested objects", () => {
279
+ const sample = {
280
+ channelKey: "test",
281
+ timeStamp: 123,
282
+ nestedObject: {
283
+ innerKey: "value",
284
+ numberArray: [1, 2, 3],
285
+ deepNesting: {
286
+ anotherKey: true,
287
+ },
288
+ },
289
+ };
290
+ const encoded = binary.MSGPACK_CODEC.encode(sample);
291
+ const decoded = binary.MSGPACK_CODEC.decode(encoded);
292
+ expect(decoded).toEqual(sample);
293
+ });
294
+
295
+ it("should handle binary data", () => {
296
+ const binaryData = new Uint8Array([1, 2, 3, 4, 5]);
297
+ const sample = {
298
+ channelKey: "binary-test",
299
+ timeStamp: 456,
300
+ value: binaryData,
301
+ };
302
+ const encoded = binary.MSGPACK_CODEC.encode(sample);
303
+ const decoded = binary.MSGPACK_CODEC.decode<typeof sampleSchema>(encoded);
304
+
305
+ // Check that the structure is preserved
306
+ expect(decoded.channelKey).toEqual(sample.channelKey);
307
+ expect(decoded.timeStamp).toEqual(sample.timeStamp);
308
+
309
+ // Verify that binary data is handled properly
310
+ // Note: The exact format might depend on msgpack implementation
311
+ expect(
312
+ Array.isArray(decoded.value) || ArrayBuffer.isView(decoded.value),
313
+ ).toBeTruthy();
314
+ });
315
+
316
+ class CustomValueEncoder {
317
+ readonly encodeValue = true;
318
+
319
+ value = "cat";
320
+ }
321
+
322
+ it("should correctly encode and decode custom value", () => {
323
+ const sample = {
324
+ channelKey: "test",
325
+ timeStamp: 123,
326
+ value: new CustomValueEncoder(),
327
+ };
328
+ const encoded = binary.MSGPACK_CODEC.encode(sample);
329
+ const decoded = binary.MSGPACK_CODEC.decode(encoded, sampleSchema);
330
+ expect(decoded).toEqual({ ...sample, value: "cat" });
331
+ });
332
+ });
333
+
334
+ describe("TextCodec", () => {
335
+ it("should correctly encode and decode text", () => {
336
+ const sampleText = "Hello, world!";
337
+ const encoded = binary.TEXT_CODEC.encode(sampleText);
338
+ const decoded = binary.TEXT_CODEC.decode(encoded);
339
+ expect(decoded).toEqual(sampleText);
340
+ });
341
+
342
+ it("should handle empty strings", () => {
343
+ const sampleText = "";
344
+ const encoded = binary.TEXT_CODEC.encode(sampleText);
345
+ const decoded = binary.TEXT_CODEC.decode(encoded);
346
+ expect(decoded).toEqual(sampleText);
347
+ });
348
+
349
+ it("should validate text with schema", () => {
350
+ const textSchema = z.string();
351
+ const sampleText = "Validation test";
352
+ const encoded = binary.TEXT_CODEC.encode(sampleText);
353
+ const decoded = binary.TEXT_CODEC.decode(encoded, textSchema);
354
+ expect(decoded).toEqual(sampleText);
355
+ });
356
+
357
+ it("should handle special characters", () => {
358
+ const sampleText = "Special characters: äöü!@#$%^&*()_+";
359
+ const encoded = binary.TEXT_CODEC.encode(sampleText);
360
+ const decoded = binary.TEXT_CODEC.decode(encoded);
361
+ expect(decoded).toEqual(sampleText);
362
+ });
363
+
364
+ it("should handle multi-line text", () => {
365
+ const sampleText = "Line 1\nLine 2\nLine 3";
366
+ const encoded = binary.TEXT_CODEC.encode(sampleText);
367
+ const decoded = binary.TEXT_CODEC.decode(encoded);
368
+ expect(decoded).toEqual(sampleText);
369
+ });
370
+ });
@@ -7,7 +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 { type z, type ZodSchema } from "zod";
10
+ import { decode, encode, ExtensionCodec } from "@msgpack/msgpack";
11
+ import { type z } from "zod";
11
12
 
12
13
  import { caseconv } from "@/caseconv";
13
14
  import { isObject } from "@/identity";
@@ -26,7 +27,7 @@ export interface Codec {
26
27
  * @param payload - The payload to encode.
27
28
  * @returns An ArrayBuffer containing the encoded payload.
28
29
  */
29
- encode: (payload: unknown) => ArrayBuffer;
30
+ encode: (payload: unknown) => Uint8Array;
30
31
 
31
32
  /**
32
33
  * Decodes the given binary representation into a type checked payload.
@@ -34,7 +35,10 @@ export interface Codec {
34
35
  * @param data - The data to decode.
35
36
  * @param schema - The schema to decode the data with.
36
37
  */
37
- decode: <P>(data: Uint8Array | ArrayBuffer, schema?: ZodSchema<P>) => P;
38
+ decode: <P extends z.ZodTypeAny>(
39
+ data: Uint8Array | ArrayBuffer,
40
+ schema?: P,
41
+ ) => z.output<P>;
38
42
  }
39
43
 
40
44
  /** JSONCodec is a JSON implementation of Codec. */
@@ -48,8 +52,8 @@ export class JSONCodec implements Codec {
48
52
  this.encoder = new TextEncoder();
49
53
  }
50
54
 
51
- encode(payload: unknown): ArrayBuffer {
52
- return this.encoder.encode(this.encodeString(payload)).buffer as ArrayBuffer;
55
+ encode(payload: unknown): Uint8Array {
56
+ return this.encoder.encode(this.encodeString(payload));
53
57
  }
54
58
 
55
59
  decode<P extends z.ZodTypeAny>(
@@ -87,9 +91,9 @@ export class JSONCodec implements Codec {
87
91
  export class CSVCodec implements Codec {
88
92
  contentType = "text/csv";
89
93
 
90
- encode(payload: unknown): ArrayBuffer {
94
+ encode(payload: unknown): Uint8Array {
91
95
  const csvString = this.encodeString(payload);
92
- return new TextEncoder().encode(csvString).buffer as ArrayBuffer;
96
+ return new TextEncoder().encode(csvString);
93
97
  }
94
98
 
95
99
  decode<P extends z.ZodTypeAny>(
@@ -155,8 +159,8 @@ export class CSVCodec implements Codec {
155
159
  export class TextCodec implements Codec {
156
160
  contentType = "text/plain";
157
161
 
158
- encode(payload: unknown): ArrayBuffer {
159
- return new TextEncoder().encode(payload as string).buffer as ArrayBuffer;
162
+ encode(payload: unknown): Uint8Array {
163
+ return new TextEncoder().encode(payload as string);
160
164
  }
161
165
 
162
166
  decode<P extends z.ZodTypeAny>(
@@ -168,8 +172,48 @@ export class TextCodec implements Codec {
168
172
  }
169
173
  }
170
174
 
175
+ const extensionCodec = new ExtensionCodec();
176
+
177
+ extensionCodec.register({
178
+ type: 0,
179
+ encode: (value: unknown): Uint8Array | null => {
180
+ if (ArrayBuffer.isView(value)) {
181
+ const array = Array.from(value as Uint8Array);
182
+ return encode(array, { extensionCodec });
183
+ }
184
+ if (isObject(value) && "encode_value" in value) {
185
+ if (typeof value.value === "bigint")
186
+ return encode(value.value.toString(), { extensionCodec });
187
+ return encode(value.value, { extensionCodec });
188
+ }
189
+ if (typeof value === "bigint") return encode(value.toString(), { extensionCodec });
190
+ return null;
191
+ },
192
+ decode: (data: Uint8Array) => decode(data, { extensionCodec }),
193
+ });
194
+
195
+ export class MsgPackCodec implements Codec {
196
+ contentType = "application/msgpack";
197
+
198
+ encode(payload: unknown): Uint8Array {
199
+ const caseConverted = caseconv.camelToSnake(payload);
200
+ const d = encode(caseConverted, { extensionCodec });
201
+ return d.slice();
202
+ }
203
+
204
+ decode<P extends z.ZodTypeAny>(
205
+ data: Uint8Array | ArrayBuffer,
206
+ schema?: P,
207
+ ): z.output<P> {
208
+ const decoded = decode(data, { extensionCodec });
209
+ const unpacked = caseconv.snakeToCamel(decoded);
210
+ return schema != null ? schema.parse(unpacked) : (unpacked as z.output<P>);
211
+ }
212
+
213
+ static registerCustomType(): void {}
214
+ }
215
+
171
216
  export const JSON_CODEC = new JSONCodec();
172
217
  export const CSV_CODEC = new CSVCodec();
173
218
  export const TEXT_CODEC = new TextCodec();
174
-
175
- export const ENCODERS: Codec[] = [JSON_CODEC];
219
+ export const MSGPACK_CODEC = new MsgPackCodec();
@@ -7,4 +7,4 @@
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
- export * as binary from "@/binary/encoder";
10
+ export * as binary from "@/binary/codec";
@@ -15,8 +15,8 @@ import { TimeSpan } from "@/telem";
15
15
  describe("breaker", () => {
16
16
  it("should allow first attempt without sleeping", async () => {
17
17
  const mockSleep = vi.fn();
18
- const brk = breaker.create({ sleepFn: mockSleep });
19
- const canRetry = await brk();
18
+ const brk = new breaker.Breaker({ sleepFn: mockSleep });
19
+ const canRetry = await brk.wait();
20
20
 
21
21
  expect(canRetry).toBe(true);
22
22
  expect(mockSleep).toHaveBeenCalled();
@@ -24,58 +24,49 @@ describe("breaker", () => {
24
24
 
25
25
  it("should retry specified number of times before failing", async () => {
26
26
  const mockSleep = vi.fn();
27
- const brk = breaker.create({
27
+ const brk = new breaker.Breaker({
28
28
  maxRetries: 2,
29
- interval: TimeSpan.milliseconds(1),
29
+ baseInterval: TimeSpan.milliseconds(1),
30
30
  sleepFn: mockSleep,
31
31
  });
32
32
 
33
33
  // First attempt
34
- expect(await brk()).toBe(true);
34
+ expect(await brk.wait()).toBe(true);
35
35
  // Second attempt
36
- expect(await brk()).toBe(true);
36
+ expect(await brk.wait()).toBe(true);
37
37
  // Third attempt (should fail)
38
- expect(await brk()).toBe(false);
38
+ expect(await brk.wait()).toBe(false);
39
39
 
40
40
  expect(mockSleep).toHaveBeenCalledTimes(2);
41
41
  });
42
42
 
43
43
  it("should increase delay between retries according to scale", async () => {
44
44
  const mockSleep = vi.fn();
45
- const brk = breaker.create({
46
- interval: TimeSpan.seconds(1),
45
+ const brk = new breaker.Breaker({
46
+ baseInterval: TimeSpan.seconds(1),
47
47
  maxRetries: 3,
48
48
  scale: 2,
49
49
  sleepFn: mockSleep,
50
50
  });
51
51
 
52
- await brk(); // First attempt - 1s
53
- await brk(); // Second attempt - 1s * 2 = 2s;
54
- await brk(); // Third attempt - 2s *2 = 4s;
52
+ await brk.wait(); // First attempt - 1s
53
+ await brk.wait(); // Second attempt - 1s * 2 = 2s;
54
+ await brk.wait(); // Third attempt - 2s *2 = 4s;
55
55
 
56
56
  expect(mockSleep).toHaveBeenNthCalledWith(1, TimeSpan.seconds(1));
57
57
  expect(mockSleep).toHaveBeenNthCalledWith(2, TimeSpan.seconds(2));
58
58
  expect(mockSleep).toHaveBeenNthCalledWith(3, TimeSpan.seconds(4));
59
59
  });
60
60
 
61
- it("should use default values when no options provided", async () => {
62
- const brk = breaker.create();
63
- let attempts = 0;
64
-
65
- while (await brk()) attempts++;
66
-
67
- expect(attempts).toBe(5); // Default maxRetries is 5
68
- });
69
-
70
61
  it("should use custom sleep function when provided", async () => {
71
62
  const customSleep = vi.fn();
72
- const brk = breaker.create({
73
- interval: TimeSpan.milliseconds(100),
63
+ const brk = new breaker.Breaker({
64
+ baseInterval: TimeSpan.milliseconds(100),
74
65
  sleepFn: customSleep,
75
66
  });
76
67
 
77
- await brk();
78
- await brk();
68
+ await brk.wait();
69
+ await brk.wait();
79
70
 
80
71
  expect(customSleep).toHaveBeenCalledTimes(2);
81
72
  });
@@ -12,31 +12,48 @@ import { z } from "zod";
12
12
  import { sleep } from "@/sleep";
13
13
  import { type CrudeTimeSpan, TimeSpan } from "@/telem";
14
14
 
15
+ export class Breaker {
16
+ private readonly config: Omit<Required<Config>, "baseInterval"> & {
17
+ baseInterval: TimeSpan;
18
+ };
19
+ private retries: number;
20
+ private interval: TimeSpan;
21
+
22
+ constructor(cfg?: Config) {
23
+ this.config = {
24
+ baseInterval: new TimeSpan(cfg?.baseInterval ?? TimeSpan.seconds(1)),
25
+ maxRetries: cfg?.maxRetries ?? 5,
26
+ scale: cfg?.scale ?? 1,
27
+ sleepFn: cfg?.sleepFn ?? sleep.sleep,
28
+ };
29
+ this.retries = 0;
30
+ this.interval = new TimeSpan(this.config.baseInterval);
31
+ }
32
+
33
+ async wait(): Promise<boolean> {
34
+ const { maxRetries, scale, sleepFn } = this.config;
35
+ if (this.retries >= maxRetries) return false;
36
+ await sleepFn(this.interval);
37
+ this.interval = this.interval.mult(scale);
38
+ this.retries++;
39
+ return true;
40
+ }
41
+
42
+ reset() {
43
+ this.retries = 0;
44
+ this.interval = this.config.baseInterval;
45
+ }
46
+ }
47
+
15
48
  export const breakerConfig = z.object({
16
- interval: TimeSpan.z.optional(),
49
+ baseInterval: TimeSpan.z.optional(),
17
50
  maxRetries: z.number().optional(),
18
51
  scale: z.number().optional(),
19
52
  });
20
53
 
21
- export interface Config extends Omit<z.infer<typeof breakerConfig>, "interval"> {
22
- interval?: CrudeTimeSpan;
54
+ export interface Config extends Omit<z.infer<typeof breakerConfig>, "baseInterval"> {
55
+ baseInterval?: CrudeTimeSpan;
23
56
  maxRetries?: number;
24
57
  scale?: number;
25
58
  sleepFn?: (duration: TimeSpan) => Promise<void>;
26
59
  }
27
-
28
- export const create = (options: Config = {}): (() => Promise<boolean>) => {
29
- const sleepFn = options.sleepFn || sleep.sleep;
30
- const maxRetries = options.maxRetries ?? 5;
31
- const scale = options.scale ?? 1;
32
- let retries = 0;
33
- let interval = new TimeSpan(options.interval ?? TimeSpan.milliseconds(1));
34
- return async () => {
35
- // Change from arrow function to regular function to preserve 'this'
36
- if (retries >= maxRetries) return false;
37
- await sleepFn(interval);
38
- interval = interval.mult(scale);
39
- retries++;
40
- return true;
41
- };
42
- };