@synnaxlabs/client 0.26.1 → 0.27.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 (125) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/README.md +36 -10
  3. package/api/client.api.md +3077 -0
  4. package/api-extractor.json +7 -0
  5. package/dist/access/client.d.ts +0 -1
  6. package/dist/access/payload.d.ts +0 -1
  7. package/dist/auth/auth.d.ts +0 -1
  8. package/dist/channel/client.d.ts +6 -2
  9. package/dist/channel/client.d.ts.map +1 -1
  10. package/dist/channel/creator.d.ts +0 -1
  11. package/dist/channel/payload.d.ts +4 -1
  12. package/dist/channel/payload.d.ts.map +1 -1
  13. package/dist/channel/retriever.d.ts +0 -1
  14. package/dist/channel/writer.d.ts +0 -1
  15. package/dist/client.cjs +17 -17
  16. package/dist/client.d.ts +6 -7
  17. package/dist/client.d.ts.map +1 -1
  18. package/dist/client.js +2006 -1745
  19. package/dist/connection/checker.d.ts +0 -1
  20. package/dist/control/client.d.ts +0 -1
  21. package/dist/control/client.d.ts.map +1 -1
  22. package/dist/control/state.d.ts +0 -1
  23. package/dist/errors.d.ts +0 -1
  24. package/dist/framer/adapter.d.ts +0 -1
  25. package/dist/framer/client.d.ts +0 -1
  26. package/dist/framer/deleter.d.ts +0 -1
  27. package/dist/framer/frame.d.ts +11 -12
  28. package/dist/framer/iterator.d.ts +0 -1
  29. package/dist/framer/streamProxy.d.ts +0 -1
  30. package/dist/framer/streamer.d.ts +0 -1
  31. package/dist/framer/writer.d.ts +0 -1
  32. package/dist/framer/writer.d.ts.map +1 -1
  33. package/dist/hardware/client.d.ts +0 -1
  34. package/dist/hardware/device/client.d.ts +0 -1
  35. package/dist/hardware/device/payload.d.ts +0 -1
  36. package/dist/hardware/device/payload.d.ts.map +1 -1
  37. package/dist/hardware/rack/client.d.ts +0 -1
  38. package/dist/hardware/rack/payload.d.ts +0 -1
  39. package/dist/hardware/rack/payload.d.ts.map +1 -1
  40. package/dist/hardware/task/client.d.ts +0 -1
  41. package/dist/hardware/task/payload.d.ts +0 -1
  42. package/dist/hardware/task/payload.d.ts.map +1 -1
  43. package/dist/label/client.d.ts +8 -6
  44. package/dist/label/client.d.ts.map +1 -1
  45. package/dist/label/payload.d.ts +0 -1
  46. package/dist/label/retriever.d.ts +0 -1
  47. package/dist/label/retriever.d.ts.map +1 -1
  48. package/dist/label/writer.d.ts +32 -2
  49. package/dist/label/writer.d.ts.map +1 -1
  50. package/dist/ontology/client.d.ts +124 -11
  51. package/dist/ontology/client.d.ts.map +1 -1
  52. package/dist/ontology/group/client.d.ts +0 -1
  53. package/dist/ontology/group/group.d.ts +0 -1
  54. package/dist/ontology/group/payload.d.ts +0 -1
  55. package/dist/ontology/group/writer.d.ts +0 -1
  56. package/dist/ontology/payload.d.ts +5 -1
  57. package/dist/ontology/payload.d.ts.map +1 -1
  58. package/dist/ontology/writer.d.ts +4 -5
  59. package/dist/ontology/writer.d.ts.map +1 -1
  60. package/dist/ranger/alias.d.ts +0 -1
  61. package/dist/ranger/client.d.ts +55 -14
  62. package/dist/ranger/client.d.ts.map +1 -1
  63. package/dist/ranger/external.d.ts +1 -1
  64. package/dist/ranger/external.d.ts.map +1 -1
  65. package/dist/ranger/kv.d.ts +42 -5
  66. package/dist/ranger/kv.d.ts.map +1 -1
  67. package/dist/ranger/payload.d.ts +5 -2
  68. package/dist/ranger/payload.d.ts.map +1 -1
  69. package/dist/ranger/writer.d.ts +107 -2
  70. package/dist/ranger/writer.d.ts.map +1 -1
  71. package/dist/setupspecs.d.ts +0 -1
  72. package/dist/signals/observable.d.ts +0 -1
  73. package/dist/transport.d.ts +0 -1
  74. package/dist/user/client.d.ts +0 -1
  75. package/dist/user/payload.d.ts +0 -1
  76. package/dist/util/retrieve.d.ts +0 -1
  77. package/dist/util/telem.d.ts +0 -1
  78. package/dist/util/zod.d.ts +0 -1
  79. package/dist/workspace/client.d.ts +0 -1
  80. package/dist/workspace/lineplot/client.d.ts +0 -1
  81. package/dist/workspace/lineplot/payload.d.ts +0 -1
  82. package/dist/workspace/lineplot/retriever.d.ts +0 -1
  83. package/dist/workspace/lineplot/writer.d.ts +0 -1
  84. package/dist/workspace/payload.d.ts +0 -1
  85. package/dist/workspace/retriever.d.ts +0 -1
  86. package/dist/workspace/schematic/client.d.ts +0 -1
  87. package/dist/workspace/schematic/payload.d.ts +0 -1
  88. package/dist/workspace/schematic/retriever.d.ts +0 -1
  89. package/dist/workspace/schematic/writer.d.ts +0 -1
  90. package/dist/workspace/writer.d.ts +0 -1
  91. package/package.json +14 -12
  92. package/src/access/access.spec.ts +11 -11
  93. package/src/channel/batchRetriever.spec.ts +2 -0
  94. package/src/channel/channel.spec.ts +51 -31
  95. package/src/channel/client.ts +7 -0
  96. package/src/channel/payload.ts +1 -0
  97. package/src/client.ts +4 -3
  98. package/src/control/client.ts +9 -0
  99. package/src/errors.spec.ts +9 -0
  100. package/src/framer/frame.spec.ts +2 -2
  101. package/src/framer/frame.ts +22 -22
  102. package/src/framer/writer.ts +2 -1
  103. package/src/hardware/device/payload.ts +9 -0
  104. package/src/hardware/rack/payload.ts +9 -0
  105. package/src/hardware/task/payload.ts +9 -0
  106. package/src/label/client.ts +49 -19
  107. package/src/label/label.spec.ts +9 -0
  108. package/src/label/retriever.ts +2 -1
  109. package/src/label/writer.ts +11 -3
  110. package/src/ontology/client.ts +214 -13
  111. package/src/ontology/payload.ts +4 -0
  112. package/src/ontology/writer.ts +26 -12
  113. package/src/ranger/client.ts +204 -28
  114. package/src/ranger/external.ts +1 -1
  115. package/src/ranger/kv.ts +50 -11
  116. package/src/ranger/payload.ts +9 -5
  117. package/src/ranger/ranger.spec.ts +114 -49
  118. package/src/ranger/writer.ts +7 -2
  119. package/vite.config.ts +1 -1
  120. package/dist/ranger/active.d.ts +0 -11
  121. package/dist/ranger/active.d.ts.map +0 -1
  122. package/dist/ranger/range.d.ts +0 -32
  123. package/dist/ranger/range.d.ts.map +0 -1
  124. package/src/ranger/active.ts +0 -74
  125. package/src/ranger/range.ts +0 -98
@@ -8,17 +8,22 @@
8
8
  // included in the file licenses/APL.txt.
9
9
 
10
10
  import { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
- import { CrudeTimeRange, TimeRange } from "@synnaxlabs/x";
11
+ import { CrudeTimeRange, observe, TimeRange } from "@synnaxlabs/x";
12
12
  import { type AsyncTermSearcher } from "@synnaxlabs/x/search";
13
+ import { type Series } from "@synnaxlabs/x/telem";
13
14
  import { toArray } from "@synnaxlabs/x/toArray";
14
15
  import { z } from "zod";
15
16
 
17
+ import { Key as ChannelKey } from "@/channel/payload";
16
18
  import { type Retriever as ChannelRetriever } from "@/channel/retriever";
17
19
  import { MultipleFoundError, NotFoundError } from "@/errors";
20
+ import { QueryError } from "@/errors";
18
21
  import { type framer } from "@/framer";
19
22
  import { type label } from "@/label";
20
- import { Active } from "@/ranger/active";
21
- import { Aliaser } from "@/ranger/alias";
23
+ import { type Label } from "@/label/payload";
24
+ import { ontology } from "@/ontology";
25
+ import { Resource } from "@/ontology/payload";
26
+ import { type Alias, Aliaser } from "@/ranger/alias";
22
27
  import { KV } from "@/ranger/kv";
23
28
  import {
24
29
  analyzeParams,
@@ -32,16 +37,169 @@ import {
32
37
  type Payload,
33
38
  payloadZ,
34
39
  } from "@/ranger/payload";
35
- import { Range } from "@/ranger/range";
36
- import { type Writer } from "@/ranger/writer";
40
+ import { CreateOptions, type Writer } from "@/ranger/writer";
37
41
  import { signals } from "@/signals";
38
42
  import { nullableArrayZ } from "@/util/zod";
39
43
 
44
+ const ontologyID = (key: string): ontology.ID =>
45
+ new ontology.ID({ type: "range", key });
46
+
47
+ export class Range {
48
+ key: string;
49
+ name: string;
50
+ readonly kv: KV;
51
+ readonly timeRange: TimeRange;
52
+ readonly color: string | undefined;
53
+ readonly channels: ChannelRetriever;
54
+ private readonly aliaser: Aliaser;
55
+ private readonly frameClient: framer.Client;
56
+ private readonly labelClient: label.Client;
57
+ private readonly ontologyClient: ontology.Client;
58
+ private readonly rangeClient: Client;
59
+
60
+ constructor(
61
+ name: string,
62
+ timeRange: TimeRange = TimeRange.ZERO,
63
+ key: string,
64
+ color: string | undefined,
65
+ _frameClient: framer.Client,
66
+ _kv: KV,
67
+ _aliaser: Aliaser,
68
+ _channels: ChannelRetriever,
69
+ _labelClient: label.Client,
70
+ _ontologyClient: ontology.Client,
71
+ _rangeClient: Client,
72
+ ) {
73
+ this.key = key;
74
+ this.name = name;
75
+ this.timeRange = timeRange;
76
+ this.frameClient = _frameClient;
77
+ this.color = color;
78
+ this.kv = _kv;
79
+ this.aliaser = _aliaser;
80
+ this.channels = _channels;
81
+ this.labelClient = _labelClient;
82
+ this.ontologyClient = _ontologyClient;
83
+ this.rangeClient = _rangeClient;
84
+ }
85
+
86
+ get ontologyID(): ontology.ID {
87
+ return new ontology.ID({ key: this.key, type: "range" });
88
+ }
89
+
90
+ get payload(): Payload {
91
+ return {
92
+ key: this.key,
93
+ name: this.name,
94
+ timeRange: this.timeRange,
95
+ color: this.color,
96
+ };
97
+ }
98
+
99
+ async setAlias(channel: ChannelKey | Name, alias: string): Promise<void> {
100
+ const ch = await this.channels.retrieve(channel);
101
+ if (ch.length === 0) {
102
+ throw new QueryError(`Channel ${channel} does not exist`);
103
+ }
104
+ await this.aliaser.set({ [ch[0].key]: alias });
105
+ }
106
+
107
+ async deleteAlias(...channels: ChannelKey[]): Promise<void> {
108
+ await this.aliaser.delete(channels);
109
+ }
110
+
111
+ async listAliases(): Promise<Record<ChannelKey, string>> {
112
+ return await this.aliaser.list();
113
+ }
114
+
115
+ async resolveAlias(alias: string): Promise<ChannelKey> {
116
+ return await this.aliaser.resolve(alias);
117
+ }
118
+
119
+ async openAliasTracker(): Promise<signals.Observable<string, Alias>> {
120
+ return await this.aliaser.openChangeTracker();
121
+ }
122
+
123
+ async retrieveParent(): Promise<Range | null> {
124
+ return this.rangeClient.retrieveParent(this.key);
125
+ }
126
+
127
+ async retrieveChildren(): Promise<Range[]> {
128
+ const res = (
129
+ await this.ontologyClient.retrieveChildren(this.ontologyID, {
130
+ excludeFieldData: true,
131
+ })
132
+ ).map((r) => r.id.key);
133
+ return await this.rangeClient.retrieve(res);
134
+ }
135
+
136
+ async read(channel: Key | Name): Promise<Series>;
137
+
138
+ async read(channels: Params): Promise<framer.Frame>;
139
+
140
+ async read(channels: Params): Promise<Series | framer.Frame> {
141
+ return await this.frameClient.read(this.timeRange, channels);
142
+ }
143
+
144
+ async labels(): Promise<Label[]> {
145
+ return await this.labelClient.retrieveFor(ontologyID(this.key));
146
+ }
147
+
148
+ async addLabel(...labels: label.Key[]): Promise<void> {
149
+ await this.labelClient.label(ontologyID(this.key), labels);
150
+ }
151
+
152
+ async removeLabel(...labels: label.Key[]): Promise<void> {
153
+ await this.labelClient.removeLabels(ontologyID(this.key), labels);
154
+ }
155
+
156
+ async openChildRangeTracker(): Promise<observe.ObservableAsyncCloseable<Range[]>> {
157
+ const wrapper = new observe.Observer<Range[]>();
158
+ const initial: ontology.Resource[] = (await this.retrieveChildren()).map((r) => {
159
+ const id = new ontology.ID({ key: r.key, type: "range" });
160
+ return { id, key: id.toString(), name: r.name, data: r.payload };
161
+ });
162
+ const base = await this.ontologyClient.openDependentTracker(
163
+ this.ontologyID,
164
+ initial,
165
+ );
166
+ base.onChange((resources: Resource[]) =>
167
+ wrapper.notify(this.rangeClient.resourcesToRanges(resources)),
168
+ );
169
+ wrapper.setCloser(async () => await base.close());
170
+ return wrapper;
171
+ }
172
+
173
+ async openParentRangeTracker(): Promise<observe.ObservableAsyncCloseable<Range> | null> {
174
+ const wrapper = new observe.Observer<Range>();
175
+ const p = await this.retrieveParent();
176
+ if (p == null) return null;
177
+ const id = new ontology.ID({ key: p.key, type: "range" });
178
+ const resourceP = { id, key: id.toString(), name: p.name, data: p.payload };
179
+ const base = await this.ontologyClient.openDependentTracker(
180
+ this.ontologyID,
181
+ [resourceP],
182
+ "parent",
183
+ "to",
184
+ );
185
+ base.onChange((resources: Resource[]) => {
186
+ const ranges = this.rangeClient.resourcesToRanges(resources);
187
+ if (ranges.length === 0) return;
188
+ const p = ranges[0];
189
+ wrapper.notify(p);
190
+ });
191
+ wrapper.setCloser(async () => await base.close());
192
+ return wrapper;
193
+ }
194
+ }
195
+
40
196
  const retrieveReqZ = z.object({
41
197
  keys: keyZ.array().optional(),
42
198
  names: z.array(z.string()).optional(),
43
199
  term: z.string().optional(),
44
200
  overlapsWith: TimeRange.z.optional(),
201
+ limit: z.number().int().optional(),
202
+ offset: z.number().int().optional(),
45
203
  });
46
204
 
47
205
  export type RetrieveRequest = z.infer<typeof retrieveReqZ>;
@@ -58,8 +216,8 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
58
216
  private readonly writer: Writer;
59
217
  private readonly unaryClient: UnaryClient;
60
218
  private readonly channels: ChannelRetriever;
61
- private readonly active: Active;
62
219
  private readonly labelClient: label.Client;
220
+ private readonly ontologyClient: ontology.Client;
63
221
 
64
222
  constructor(
65
223
  frameClient: framer.Client,
@@ -67,22 +225,26 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
67
225
  unary: UnaryClient,
68
226
  channels: ChannelRetriever,
69
227
  labelClient: label.Client,
228
+ ontologyClient: ontology.Client,
70
229
  ) {
71
230
  this.frameClient = frameClient;
72
231
  this.writer = writer;
73
232
  this.unaryClient = unary;
74
233
  this.channels = channels;
75
- this.active = new Active(unary);
76
234
  this.labelClient = labelClient;
235
+ this.ontologyClient = ontologyClient;
77
236
  }
78
237
 
79
- async create(range: NewPayload): Promise<Range>;
238
+ async create(range: NewPayload, options?: CreateOptions): Promise<Range>;
80
239
 
81
- async create(ranges: NewPayload[]): Promise<Range[]>;
240
+ async create(ranges: NewPayload[], options?: CreateOptions): Promise<Range[]>;
82
241
 
83
- async create(ranges: NewPayload | NewPayload[]): Promise<Range | Range[]> {
242
+ async create(
243
+ ranges: NewPayload | NewPayload[],
244
+ options?: CreateOptions,
245
+ ): Promise<Range | Range[]> {
84
246
  const single = !Array.isArray(ranges);
85
- const res = this.sugar(await this.writer.create(toArray(ranges)));
247
+ const res = this.sugar(await this.writer.create(toArray(ranges), options));
86
248
  return single ? res[0] : res;
87
249
  }
88
250
 
@@ -98,8 +260,8 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
98
260
  return this.sugar(await this.execRetrieve({ term }));
99
261
  }
100
262
 
101
- async page(): Promise<Range[]> {
102
- return [];
263
+ async page(offset: number, limit: number): Promise<Range[]> {
264
+ return this.sugar(await this.execRetrieve({ offset, limit }));
103
265
  }
104
266
 
105
267
  async retrieve(range: CrudeTimeRange): Promise<Range[]>;
@@ -111,7 +273,8 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
111
273
  async retrieve(params: Params | CrudeTimeRange): Promise<Range | Range[]> {
112
274
  if (typeof params === "object" && "start" in params)
113
275
  return await this.execRetrieve({ overlapsWith: new TimeRange(params) });
114
- const { single, actual, variant, normalized } = analyzeParams(params);
276
+ const { single, actual, variant, normalized, empty } = analyzeParams(params);
277
+ if (empty) return [];
115
278
  const ranges = await this.execRetrieve({ [variant]: normalized });
116
279
  if (!single) return ranges;
117
280
  if (ranges.length === 0)
@@ -121,6 +284,10 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
121
284
  return ranges[0];
122
285
  }
123
286
 
287
+ getKV(range: Key): KV {
288
+ return new KV(range, this.unaryClient, this.frameClient);
289
+ }
290
+
124
291
  private async execRetrieve(req: RetrieveRequest): Promise<Range[]> {
125
292
  const { ranges } = await sendRequired<typeof retrieveReqZ, typeof retrieveResZ>(
126
293
  this.unaryClient,
@@ -132,21 +299,18 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
132
299
  return this.sugar(ranges);
133
300
  }
134
301
 
135
- async setActive(range: Key): Promise<void> {
136
- await this.active.setActive(range);
137
- }
138
-
139
- async retrieveActive(): Promise<Range | null> {
140
- const res = await this.active.retrieveActive();
141
- if (res == null) return null;
142
- return this.sugar([res])[0];
143
- }
144
-
145
- async clearActive(range: Key): Promise<void> {
146
- await this.active.clearActive(range);
302
+ async retrieveParent(range: Key): Promise<Range | null> {
303
+ const res = await this.ontologyClient.retrieveParents({
304
+ key: range,
305
+ type: "range",
306
+ });
307
+ if (res.length === 0) return null;
308
+ const first = res[0];
309
+ if (first.id.type !== "range") return null;
310
+ return await this.retrieve(first.id.key);
147
311
  }
148
312
 
149
- private sugar(payloads: Payload[]): Range[] {
313
+ sugar(payloads: Payload[]): Range[] {
150
314
  return payloads.map((payload) => {
151
315
  return new Range(
152
316
  payload.name,
@@ -154,10 +318,12 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
154
318
  payload.key,
155
319
  payload.color,
156
320
  this.frameClient,
157
- new KV(payload.key, this.unaryClient),
321
+ new KV(payload.key, this.unaryClient, this.frameClient),
158
322
  new Aliaser(payload.key, this.frameClient, this.unaryClient),
159
323
  this.channels,
160
324
  this.labelClient,
325
+ this.ontologyClient,
326
+ this,
161
327
  );
162
328
  });
163
329
  }
@@ -175,4 +341,14 @@ export class Client implements AsyncTermSearcher<string, Key, Range> {
175
341
  },
176
342
  );
177
343
  }
344
+
345
+ resourcesToRanges(resources: Resource[]): Range[] {
346
+ return this.sugar(
347
+ resources.map((r) => ({
348
+ key: r.id.key,
349
+ name: r.data?.name as string,
350
+ timeRange: new TimeRange(r.data?.timeRange as CrudeTimeRange),
351
+ })),
352
+ );
353
+ }
178
354
  }
@@ -9,6 +9,6 @@
9
9
 
10
10
  export type { Alias, AliasChange } from "@/ranger/alias";
11
11
  export * from "@/ranger/client";
12
+ export * from "@/ranger/kv";
12
13
  export * from "@/ranger/payload";
13
- export * from "@/ranger/range";
14
14
  export * from "@/ranger/writer";
package/src/ranger/kv.ts CHANGED
@@ -7,27 +7,36 @@
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 { sendRequired, type UnaryClient } from "@synnaxlabs/freighter";
11
11
  import { isObject } from "@synnaxlabs/x/identity";
12
12
  import { toArray } from "@synnaxlabs/x/toArray";
13
13
  import { z } from "zod";
14
14
 
15
+ import { framer } from "@/framer";
15
16
  import { type Key, keyZ } from "@/ranger/payload";
17
+ import { signals } from "@/signals";
18
+ import { nullableArrayZ } from "@/util/zod";
16
19
 
17
20
  const getReqZ = z.object({
18
21
  range: keyZ,
19
22
  keys: z.string().array(),
20
23
  });
21
24
 
25
+ const kvPairZ = z.object({
26
+ range: keyZ,
27
+ key: z.string(),
28
+ value: z.string(),
29
+ });
30
+
22
31
  const getResZ = z.object({
23
- pairs: z.record(z.string(), z.string()),
32
+ pairs: nullableArrayZ(kvPairZ),
24
33
  });
25
34
 
26
35
  export type GetRequest = z.infer<typeof getReqZ>;
27
36
 
28
37
  const setReqZ = z.object({
29
38
  range: keyZ,
30
- pairs: z.record(z.string(), z.string()),
39
+ pairs: kvPairZ.array(),
31
40
  });
32
41
 
33
42
  export type SetRequest = z.infer<typeof setReqZ>;
@@ -39,16 +48,20 @@ const deleteReqZ = z.object({
39
48
 
40
49
  export type DeleteRequest = z.infer<typeof deleteReqZ>;
41
50
 
51
+ export type KVPair = z.infer<typeof kvPairZ>;
52
+
42
53
  export class KV {
43
54
  private static readonly GET_ENDPOINT = "/range/kv/get";
44
55
  private static readonly SET_ENDPOINT = "/range/kv/set";
45
56
  private static readonly DELETE_ENDPOINT = "/range/kv/delete";
46
57
  private readonly rangeKey: Key;
47
58
  private readonly client: UnaryClient;
59
+ private readonly frameClient: framer.Client;
48
60
 
49
- constructor(rng: Key, client: UnaryClient) {
61
+ constructor(rng: Key, client: UnaryClient, frameClient: framer.Client) {
50
62
  this.rangeKey = rng;
51
63
  this.client = client;
64
+ this.frameClient = frameClient;
52
65
  }
53
66
 
54
67
  async get(key: string): Promise<string>;
@@ -56,14 +69,15 @@ export class KV {
56
69
  async get(keys: string[]): Promise<Record<string, string>>;
57
70
 
58
71
  async get(keys: string | string[]): Promise<string | Record<string, string>> {
59
- const [res, err] = await this.client.send(
72
+ const res = await sendRequired(
73
+ this.client,
60
74
  KV.GET_ENDPOINT,
61
75
  { range: this.rangeKey, keys: toArray(keys) },
62
76
  getReqZ,
63
77
  getResZ,
64
78
  );
65
- if (err != null) throw err;
66
- return Array.isArray(keys) ? res.pairs : res.pairs[keys];
79
+ if (typeof keys === "string") return res.pairs[0].value;
80
+ return Object.fromEntries(res.pairs.map((pair) => [pair.key, pair.value]));
67
81
  }
68
82
 
69
83
  async list(): Promise<Record<string, string>> {
@@ -75,13 +89,18 @@ export class KV {
75
89
  async set(kv: Record<string, string>): Promise<void>;
76
90
 
77
91
  async set(key: string | Record<string, string>, value: string = ""): Promise<void> {
92
+ let pairs: KVPair[];
93
+ if (isObject(key))
94
+ pairs = Object.entries(key).map(([k, v]) => ({
95
+ range: this.rangeKey,
96
+ key: k,
97
+ value: v,
98
+ }));
99
+ else pairs = [{ range: this.rangeKey, key: key, value: value }];
78
100
  await sendRequired(
79
101
  this.client,
80
102
  KV.SET_ENDPOINT,
81
- {
82
- range: this.rangeKey,
83
- pairs: isObject(key) ? key : { [key]: value },
84
- },
103
+ { range: this.rangeKey, pairs },
85
104
  setReqZ,
86
105
  z.unknown(),
87
106
  );
@@ -96,4 +115,24 @@ export class KV {
96
115
  z.unknown(),
97
116
  );
98
117
  }
118
+
119
+ async openTracker(): Promise<signals.Observable<string, KVPair>> {
120
+ return await signals.openObservable<string, KVPair>(
121
+ this.frameClient,
122
+ "sy_range_kv_set",
123
+ "sy_range_kv_delete",
124
+ (variant, data) => {
125
+ if (variant === "delete")
126
+ return data.toStrings().map((combinedKey) => {
127
+ const [range, key] = combinedKey.split("<--->", 2);
128
+ return { variant, key: combinedKey, value: { range, key, value: "" } };
129
+ });
130
+ return data.parseJSON(kvPairZ).map((pair) => ({
131
+ variant,
132
+ key: `${pair.range}${pair.key}`,
133
+ value: pair,
134
+ }));
135
+ },
136
+ );
137
+ }
99
138
  }
@@ -31,7 +31,7 @@ export type Payload = z.infer<typeof payloadZ>;
31
31
  export const newPayloadZ = payloadZ.extend({
32
32
  key: z.string().uuid().optional(),
33
33
  });
34
- export type NewPayload = z.infer<typeof newPayloadZ>;
34
+ export type NewPayload = z.input<typeof newPayloadZ>;
35
35
 
36
36
  export type ParamAnalysisResult =
37
37
  | {
@@ -39,37 +39,41 @@ export type ParamAnalysisResult =
39
39
  variant: "keys";
40
40
  normalized: Keys;
41
41
  actual: Key;
42
+ empty: never;
42
43
  }
43
44
  | {
44
45
  single: true;
45
46
  variant: "names";
46
47
  normalized: Names;
47
48
  actual: Name;
49
+ empty: never;
48
50
  }
49
51
  | {
50
52
  single: false;
51
53
  variant: "keys";
52
54
  normalized: Keys;
53
55
  actual: Keys;
56
+ empty: boolean;
54
57
  }
55
58
  | {
56
59
  single: false;
57
60
  variant: "names";
58
61
  normalized: Names;
59
62
  actual: Names;
63
+ empty: boolean;
60
64
  };
61
65
 
62
66
  export const analyzeParams = (params: Params): ParamAnalysisResult => {
63
67
  const normal = toArray(params) as Keys | Names;
64
- if (normal.length === 0) {
65
- throw new Error("Range params must not be empty");
66
- }
67
- const isKey = keyZ.safeParse(normal[0]).success;
68
+ const empty = normal.length === 0;
69
+ let isKey = false;
70
+ if (!empty) isKey = keyZ.safeParse(normal[0]).success;
68
71
  return {
69
72
  single: !Array.isArray(params),
70
73
  variant: isKey ? "keys" : "names",
71
74
  normalized: normal,
72
75
  actual: params,
76
+ empty,
73
77
  } as const as ParamAnalysisResult;
74
78
  };
75
79