libshv-js 3.3.6 → 3.4.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.
package/src/rpcmessage.ts CHANGED
@@ -1,15 +1,13 @@
1
- import {type IMap, type Int, isIMap, makeIMap, makeMetaMap, type MetaMap, type RpcValue, RpcValueWithMetaData} from './rpcvalue';
2
- import {toCpon} from './cpon';
3
- import {toChainPack} from './chainpack';
1
+ import * as z from './zod';
4
2
 
5
- const TagRequestId = 8;
6
- const TagShvPath = 9;
7
- const TagMethod = 10;
8
- const TagCallerIds = 11;
3
+ export const RPC_MESSAGE_REQUEST_ID = 8;
4
+ export const RPC_MESSAGE_SHV_PATH = 9;
5
+ export const RPC_MESSAGE_METHOD = 10;
6
+ export const RPC_MESSAGE_CALLER_IDS = 11;
9
7
 
10
- const KeyParams = 1;
11
- const KeyResult = 2;
12
- const KeyError = 3;
8
+ export const RPC_MESSAGE_PARAMS = 1;
9
+ export const RPC_MESSAGE_RESULT = 2;
10
+ export const RPC_MESSAGE_ERROR = 3;
13
11
 
14
12
  export const ERROR_CODE = 1;
15
13
  export const ERROR_MESSAGE = 2;
@@ -30,169 +28,52 @@ enum ErrorCode {
30
28
  NotImplemented = 12,
31
29
  }
32
30
 
33
- export type ErrorMap = {
34
- [ERROR_CODE]: ErrorCode;
35
- [ERROR_MESSAGE]?: string;
36
- [ERROR_DATA]?: RpcValue;
37
- };
38
-
39
- class RpcError extends Error {
40
- constructor(private readonly err_info: ErrorMap) {
41
- super(err_info[ERROR_MESSAGE] ?? 'Unknown RpcError');
42
- }
43
-
44
- data() {
45
- return this.err_info[ERROR_DATA];
46
- }
47
- }
48
-
49
- export class ProtocolError extends Error {}
50
-
51
- export class InvalidRequest extends RpcError {}
52
- export class MethodNotFound extends RpcError {}
53
- export class InvalidParams extends RpcError {}
54
- export class InternalError extends RpcError {}
55
- export class ParseError extends RpcError {}
56
- export class MethodCallTimeout extends RpcError {}
57
- export class MethodCallCancelled extends RpcError {}
58
- export class MethodCallException extends RpcError {}
59
- export class Unknown extends RpcError {}
60
- export class LoginRequired extends RpcError {}
61
- export class UserIDRequired extends RpcError {}
62
- export class NotImplemented extends RpcError {}
63
-
64
- class RpcMessage {
65
- value: IMap;
66
- meta: MetaMap;
67
- constructor(rpc_val?: RpcValue) {
68
- if (rpc_val === undefined) {
69
- this.value = makeIMap({});
70
- this.meta = makeMetaMap({});
71
- return;
72
- }
73
-
74
- if (!(rpc_val instanceof RpcValueWithMetaData && isIMap(rpc_val.value))) {
75
- throw new TypeError(`RpcMessage initialized with a non-IMap: ${toCpon(rpc_val)}`);
76
- }
77
-
78
- this.value = rpc_val.value;
79
- this.meta = rpc_val.meta;
80
- }
81
-
82
- isValid() {
83
- return this.shvPath() && (this.isRequest() || this.isResponse || this.isSignal());
84
- }
85
-
86
- isRequest(): boolean {
87
- return this.requestId() !== undefined && this.method() !== undefined;
88
- }
89
-
90
- isResponse(): boolean {
91
- return this.requestId() !== undefined && this.method() === undefined;
92
- }
93
-
94
- isSignal(): boolean {
95
- return this.requestId() === undefined && this.method() !== undefined;
96
- }
97
-
98
- requestId(): Int | undefined {
99
- return (this.meta[TagRequestId] as Int);
100
- }
101
-
102
- setRequestId(id: number) {
103
- this.meta[TagRequestId] = id;
104
- }
105
-
106
- callerIds(): RpcValue[] | undefined {
107
- return this.meta[TagCallerIds] as RpcValue[];
108
- }
109
-
110
- setCallerIds(ids: RpcValue[]) {
111
- this.meta[TagCallerIds] = ids;
112
- }
113
-
114
- shvPath(): string | undefined {
115
- return (this.meta[TagShvPath] as string);
116
- }
117
-
118
- setShvPath(val: string) {
119
- this.meta[TagShvPath] = val;
120
- }
121
-
122
- method(): string | undefined {
123
- return (this.meta[TagMethod] as string);
124
- }
125
-
126
- setMethod(val: string) {
127
- this.meta[TagMethod] = val;
128
- }
129
-
130
- params() {
131
- return this.value[KeyParams];
132
- }
133
-
134
- setParams(params: RpcValue) {
135
- this.value[KeyParams] = params;
136
- }
137
-
138
- resultOrError() {
139
- if (Object.hasOwn(this.value, KeyError)) {
140
- if (!isIMap(this.value[KeyError])) {
141
- return new ProtocolError('Response had an error, but this error was not a map');
142
- }
143
-
144
- const error_map = this.value[KeyError];
145
- if (typeof error_map[ERROR_CODE] !== 'number') {
146
- return new ProtocolError('Response had an error, but this error did not contain at least an error code');
147
- }
148
-
149
- const code = error_map[ERROR_CODE] as unknown;
150
-
151
- const ErrorType = (() => {
152
- switch (code) {
153
- case ErrorCode.InvalidRequest: return InvalidRequest;
154
- case ErrorCode.MethodNotFound: return MethodNotFound;
155
- case ErrorCode.InvalidParams: return InvalidParams;
156
- case ErrorCode.InternalError: return InternalError;
157
- case ErrorCode.ParseError: return ParseError;
158
- case ErrorCode.MethodCallTimeout: return MethodCallTimeout;
159
- case ErrorCode.MethodCallCancelled: return MethodCallCancelled;
160
- case ErrorCode.MethodCallException: return MethodCallException;
161
- case ErrorCode.Unknown: return Unknown;
162
- case ErrorCode.LoginRequired: return LoginRequired;
163
- case ErrorCode.UserIDRequired: return UserIDRequired;
164
- case ErrorCode.NotImplemented: return NotImplemented;
165
- default: return Unknown;
166
- }
167
- })();
168
-
169
- return new ErrorType(this.value[KeyError] as unknown as ErrorMap);
170
- }
171
-
172
- if (Object.hasOwn(this.value, KeyResult)) {
173
- return this.value[KeyResult];
174
- }
175
-
176
- return new ProtocolError('Response included neither result nor error');
177
- }
178
-
179
- setResult(result: RpcValue) {
180
- this.value[KeyResult] = result;
181
- }
182
-
183
- setError(error: string) {
184
- this.value[KeyError] = error;
185
- }
186
-
187
- toCpon() {
188
- return toCpon(new RpcValueWithMetaData(this.value, this.meta));
189
- }
190
-
191
- toChainPack() {
192
- return toChainPack(new RpcValueWithMetaData(this.value, this.meta));
193
- }
194
- }
195
-
196
- export type RpcResponse<T = RpcValue> = T | Error;
197
-
198
- export {RpcMessage, RpcError, ErrorCode};
31
+ const ErrorMapZod = z.imap({
32
+ [ERROR_CODE]: z.number(),
33
+ [ERROR_MESSAGE]: z.string().optional(),
34
+ [ERROR_DATA]: z.rpcvalue().optional(),
35
+ });
36
+
37
+ export type ErrorMap = z.infer<typeof ErrorMapZod>;
38
+
39
+ const RpcRequestMetaZod = z.metamap({
40
+ [RPC_MESSAGE_REQUEST_ID]: z.number(),
41
+ [RPC_MESSAGE_METHOD]: z.string(),
42
+ [RPC_MESSAGE_SHV_PATH]: z.string(),
43
+ });
44
+
45
+ const RpcRequestValueZod = z.imap({
46
+ [RPC_MESSAGE_PARAMS]: z.rpcvalue().optional(),
47
+ });
48
+
49
+ const RpcResponseMetaZod = z.metamap({
50
+ [RPC_MESSAGE_REQUEST_ID]: z.number(),
51
+ });
52
+ const RpcResponseValueZod = z.imap({
53
+ [RPC_MESSAGE_RESULT]: z.rpcvalue(),
54
+ }).or(z.imap({
55
+ [RPC_MESSAGE_ERROR]: ErrorMapZod,
56
+ }));
57
+
58
+ const RpcSignalMetaZod = z.metamap({
59
+ [RPC_MESSAGE_SHV_PATH]: z.string(),
60
+ [RPC_MESSAGE_METHOD]: z.string(),
61
+ });
62
+ const RpcSignalValueZod = z.imap({
63
+ [RPC_MESSAGE_PARAMS]: z.rpcvalue().optional(),
64
+ });
65
+
66
+ const RpcRequestZod = z.withMeta(RpcRequestMetaZod, RpcRequestValueZod);
67
+ const RpcResponseZod = z.withMeta(RpcResponseMetaZod, RpcResponseValueZod);
68
+ const RpcSignalZod = z.withMeta(RpcSignalMetaZod, RpcSignalValueZod);
69
+ const RpcMessageZod = z.union([RpcRequestZod, RpcResponseZod, RpcSignalZod]);
70
+ export type RpcRequest = z.infer<typeof RpcRequestZod>;
71
+ export type RpcResponse = z.infer<typeof RpcResponseZod>;
72
+ export type RpcSignal = z.infer<typeof RpcSignalZod>;
73
+ export type RpcMessage = z.infer<typeof RpcMessageZod>;
74
+
75
+ export const isSignal = (message: RpcMessage): message is RpcSignal => !(RPC_MESSAGE_REQUEST_ID in message.meta) && RPC_MESSAGE_METHOD in message.meta;
76
+ export const isRequest = (message: RpcMessage): message is RpcRequest => RPC_MESSAGE_REQUEST_ID in message.meta && RPC_MESSAGE_METHOD in message.meta;
77
+ export const isResponse = (message: RpcMessage): message is RpcResponse => RPC_MESSAGE_REQUEST_ID in message.meta && !(RPC_MESSAGE_METHOD in message.meta);
78
+
79
+ export {RpcMessageZod, ErrorCode};
package/src/rpcvalue.ts CHANGED
@@ -50,10 +50,11 @@ export type Blob = ArrayBuffer;
50
50
  export type ShvString = string;
51
51
  export type DateTime = Date & {utc_offset?: number};
52
52
  const withOffset = (date: Date, utc_offset?: number) => {
53
- const cloned_date: DateTime = new Date(date.getTime());
54
- cloned_date.utc_offset = utc_offset;
55
- return cloned_date;
53
+ const clonedDate: DateTime = new Date(date.getTime());
54
+ clonedDate.utc_offset = utc_offset;
55
+ return clonedDate;
56
56
  };
57
+
57
58
  export type List = RpcValue[];
58
59
 
59
60
  const shvMapType = Symbol('shvMapType');
@@ -92,9 +93,11 @@ type MetaMap<T extends Record<string | number, RpcValue> = Record<string | numbe
92
93
  [shvMapType]: 'metamap';
93
94
  };
94
95
 
95
- const isShvMap = (x: RpcValue): x is ShvMap => typeof x === 'object' && (x as ShvMap)[shvMapType] === 'map';
96
+ const isShvMap = (x: unknown): x is ShvMap => typeof x === 'object' && (x as ShvMap)[shvMapType] === 'map';
97
+
98
+ const isIMap = (x: unknown): x is IMap => typeof x === 'object' && (x as IMap)[shvMapType] === 'imap';
96
99
 
97
- const isIMap = (x: RpcValue): x is IMap => typeof x === 'object' && (x as IMap)[shvMapType] === 'imap';
100
+ const isMetaMap = (x: unknown): x is IMap => typeof x === 'object' && (x as MetaMap)[shvMapType] === 'metamap';
98
101
 
99
102
  const makeMetaMap = <T extends Record<string | number, RpcValue> = Record<string | number, RpcValue>, U extends Record<number, RpcValue> = Omit<T, typeof shvMapType>>(x: U = {} as U): MetaMap<U> => ({
100
103
  ...x,
@@ -111,10 +114,10 @@ const makeMap = <T extends Record<string, RpcValue> = Record<string, RpcValue>,
111
114
  [shvMapType]: 'map',
112
115
  });
113
116
 
114
- class RpcValueWithMetaData {
115
- constructor(public value: RpcValueType, public meta: MetaMap) {}
117
+ class RpcValueWithMetaData<MetaSchema extends MetaMap = MetaMap, ValueSchema extends RpcValueType = RpcValueType> {
118
+ constructor(public meta: MetaSchema, public value: ValueSchema) {}
116
119
  }
117
120
 
118
121
  export type RpcValue = RpcValueType | RpcValueWithMetaData;
119
122
 
120
- export {shvMapType, Decimal, Double, type IMap, type MetaMap, RpcValueWithMetaData, type ShvMap, UInt, withOffset, makeMap, makeIMap, makeMetaMap, isIMap, isShvMap};
123
+ export {shvMapType, Decimal, Double, type IMap, type MetaMap, RpcValueWithMetaData, type ShvMap, UInt, withOffset, makeMap, makeIMap, makeMetaMap, isIMap, isMetaMap, isShvMap};
package/src/ws-client.ts CHANGED
@@ -1,7 +1,7 @@
1
- import {ChainPackReader, ChainpackProtocolType, ChainPackWriter} from './chainpack';
2
- import {type CponReader, CponProtocolType} from './cpon';
3
- import {ERROR_MESSAGE, ErrorCode, ERROR_CODE, RpcMessage, type RpcResponse, MethodCallTimeout} from './rpcmessage';
4
- import {type RpcValue, type Null, type Int, type IMap, type ShvMap, makeMap, makeIMap} from './rpcvalue';
1
+ import {ChainPackReader, CHAINPACK_PROTOCOL_TYPE, ChainPackWriter, toChainPack} from './chainpack';
2
+ import {type CponReader, CPON_PROTOCOL_TYPE, toCpon} from './cpon';
3
+ import {ERROR_MESSAGE, ErrorCode, ERROR_CODE, RpcMessageZod, type RpcMessage, isSignal, isRequest, type RpcRequest, isResponse, ERROR_DATA, type ErrorMap, RPC_MESSAGE_METHOD, RPC_MESSAGE_SHV_PATH, RPC_MESSAGE_REQUEST_ID, RPC_MESSAGE_PARAMS, RPC_MESSAGE_ERROR, RPC_MESSAGE_RESULT} from './rpcmessage';
4
+ import {type RpcValue, type Null, type Int, type IMap, type ShvMap, makeMap, makeIMap, RpcValueWithMetaData, makeMetaMap} from './rpcvalue';
5
5
 
6
6
  const DEFAULT_TIMEOUT = 5000;
7
7
  const DEFAULT_PING_INTERVAL = 30 * 1000;
@@ -11,20 +11,23 @@ const dataToRpcValue = (buff: ArrayBuffer) => {
11
11
  rd.readUIntData();
12
12
  const proto = rd.ctx.getByte();
13
13
  switch (proto) {
14
- case ChainpackProtocolType:
14
+ case CHAINPACK_PROTOCOL_TYPE:
15
15
  break;
16
- case CponProtocolType:
16
+ case CPON_PROTOCOL_TYPE:
17
17
  throw new Error('CPON protocol type is not supported anymore');
18
18
  default:
19
19
  throw new Error(`Unsupported protocol type ${proto}`);
20
20
  }
21
- const rpc_val = rd.read();
22
- return rpc_val;
21
+
22
+ const rpcVal = rd.read();
23
+ return rpcVal;
23
24
  };
24
25
 
25
26
  type SubscriptionCallback = (path: string, method: string, param?: RpcValue) => void;
26
27
 
27
- type RpcResponseResolver = (rpc_msg: RpcResponse) => void;
28
+ type ResultOrError<T = RpcValue> = T | Error;
29
+
30
+ type RpcResponseResolver = (rpc_msg: ResultOrError) => void;
28
31
 
29
32
  type Subscription = {
30
33
  subscriber: string;
@@ -45,7 +48,7 @@ type WsClientOptions = {
45
48
  onConnected: () => void;
46
49
  onConnectionFailure: (error: Error) => void;
47
50
  onDisconnected: () => void;
48
- onRequest: (rpc_msg: RpcMessage) => void;
51
+ onRequest: (rpc_msg: RpcRequest) => void;
49
52
  };
50
53
 
51
54
  type LsResult = string[];
@@ -74,6 +77,31 @@ type DirResult = Array<IMap<{
74
77
  [DIR_EXTRA]: ShvMap;
75
78
  }>>;
76
79
 
80
+ class RpcError extends Error {
81
+ constructor(private readonly err_info: ErrorMap) {
82
+ super(err_info[ERROR_MESSAGE] ?? 'Unknown RpcError');
83
+ }
84
+
85
+ data() {
86
+ return this.err_info[ERROR_DATA];
87
+ }
88
+ }
89
+
90
+ class ProtocolError extends Error {}
91
+
92
+ class InvalidRequest extends RpcError {}
93
+ class MethodNotFound extends RpcError {}
94
+ class InvalidParams extends RpcError {}
95
+ class InternalError extends RpcError {}
96
+ class ParseError extends RpcError {}
97
+ class MethodCallTimeout extends RpcError {}
98
+ class MethodCallCancelled extends RpcError {}
99
+ class MethodCallException extends RpcError {}
100
+ class Unknown extends RpcError {}
101
+ class LoginRequired extends RpcError {}
102
+ class UserIDRequired extends RpcError {}
103
+ class NotImplemented extends RpcError {}
104
+
77
105
  class WsClient {
78
106
  requestId = 1;
79
107
  pingTimerId = -1;
@@ -127,11 +155,13 @@ class WsClient {
127
155
  this.logDebug('FAILURE: couldn\'t perform initial handshake', error.message);
128
156
  this.onConnectionFailure(error);
129
157
  };
158
+
130
159
  this.callRpcMethod(undefined, 'hello').then(response => {
131
160
  if (response instanceof Error) {
132
161
  handleConnectionError(response);
133
162
  return;
134
163
  }
164
+
135
165
  const params = makeMap({
136
166
  login: makeMap({
137
167
  password: this.password,
@@ -148,6 +178,7 @@ class WsClient {
148
178
  handleConnectionError(response);
149
179
  return;
150
180
  }
181
+
151
182
  this.logDebug('SUCCESS: connected to shv broker');
152
183
  this.onConnected();
153
184
  this.pingTimerId = window.setInterval(() => {
@@ -165,32 +196,58 @@ class WsClient {
165
196
  window.clearInterval(this.pingTimerId);
166
197
  });
167
198
 
168
- this.websocket.addEventListener('message', evt => {
169
- const rpc_val = dataToRpcValue(evt.data);
170
- const rpc_msg = new RpcMessage(rpc_val);
171
- this.logDebug(`message received: ${rpc_msg.toCpon()}`);
199
+ this.websocket.addEventListener('message', (evt: MessageEvent<ArrayBuffer>) => {
200
+ const rpcVal = dataToRpcValue(evt.data);
201
+ const rpcMsg = RpcMessageZod.parse(rpcVal);
202
+ this.logDebug(`message received: ${toCpon(rpcMsg)}`);
172
203
 
173
- if (rpc_msg.isSignal()) {
204
+ if (isSignal(rpcMsg)) {
174
205
  for (const sub of this.subscriptions) {
175
- const shv_path = rpc_msg.shvPath();
176
- const method = rpc_msg.method();
206
+ const shvPath = rpcMsg.meta[RPC_MESSAGE_SHV_PATH];
207
+ const method = rpcMsg.meta[RPC_MESSAGE_METHOD];
177
208
 
178
- if (shv_path?.startsWith(sub.path) && method === sub.method) {
179
- sub.callback(shv_path, method, rpc_msg.params());
209
+ if (shvPath.startsWith(sub.path) && method === sub.method) {
210
+ sub.callback(shvPath, method, rpcMsg.value[RPC_MESSAGE_PARAMS]);
180
211
  }
181
212
  }
182
- } else if (rpc_msg.isRequest()) {
183
- this.onRequest(rpc_msg);
184
- } else if (rpc_msg.isResponse()) {
185
- const requestId = rpc_msg.requestId();
186
- if (requestId === undefined) {
187
- throw new Error('got RpcResponse without requestId');
188
- }
213
+ } else if (isRequest(rpcMsg)) {
214
+ this.onRequest(rpcMsg);
215
+ } else if (isResponse(rpcMsg)) {
216
+ const requestId = rpcMsg.meta[RPC_MESSAGE_REQUEST_ID];
189
217
 
190
218
  if (this.rpcHandlers[Number(requestId)] !== undefined) {
191
219
  const handler = this.rpcHandlers[Number(requestId)];
192
220
  clearTimeout(handler.timeout_handle);
193
- handler.resolve(rpc_msg.resultOrError());
221
+ handler.resolve((() => {
222
+ if (RPC_MESSAGE_ERROR in rpcMsg.value) {
223
+ const code = rpcMsg.value[RPC_MESSAGE_ERROR][ERROR_CODE] as unknown;
224
+ const ErrorTypeCtor = (() => {
225
+ switch (code) {
226
+ case ErrorCode.InvalidRequest: return InvalidRequest;
227
+ case ErrorCode.MethodNotFound: return MethodNotFound;
228
+ case ErrorCode.InvalidParams: return InvalidParams;
229
+ case ErrorCode.InternalError: return InternalError;
230
+ case ErrorCode.ParseError: return ParseError;
231
+ case ErrorCode.MethodCallTimeout: return MethodCallTimeout;
232
+ case ErrorCode.MethodCallCancelled: return MethodCallCancelled;
233
+ case ErrorCode.MethodCallException: return MethodCallException;
234
+ case ErrorCode.Unknown: return Unknown;
235
+ case ErrorCode.LoginRequired: return LoginRequired;
236
+ case ErrorCode.UserIDRequired: return UserIDRequired;
237
+ case ErrorCode.NotImplemented: return NotImplemented;
238
+ default: return Unknown;
239
+ }
240
+ })();
241
+
242
+ return new ErrorTypeCtor(rpcMsg.value[RPC_MESSAGE_ERROR]);
243
+ }
244
+
245
+ if (RPC_MESSAGE_RESULT in rpcMsg.value) {
246
+ return rpcMsg.value[RPC_MESSAGE_RESULT];
247
+ }
248
+
249
+ return new ProtocolError('Response included neither result nor error');
250
+ })());
194
251
  // eslint-disable-next-line @typescript-eslint/no-array-delete, @typescript-eslint/no-dynamic-delete
195
252
  delete this.rpcHandlers[Number(requestId)];
196
253
  }
@@ -203,28 +260,29 @@ class WsClient {
203
260
  });
204
261
  }
205
262
 
206
- callRpcMethod(shv_path: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string]): Promise<RpcResponse<string>>;
207
- callRpcMethod(shv_path: string | undefined, method: 'dir', params?: RpcValue): Promise<RpcResponse<DirResult>>;
208
- callRpcMethod(shv_path: string | undefined, method: 'ls', params?: RpcValue): Promise<RpcResponse<LsResult>>;
209
- callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue): Promise<RpcResponse>;
210
- callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue) {
211
- const rq = new RpcMessage();
212
- const rq_id = this.requestId++;
213
- rq.setRequestId(rq_id);
214
- if (shv_path !== undefined) {
215
- rq.setShvPath(shv_path);
216
- }
217
- rq.setMethod(method);
218
- if (params !== undefined) {
219
- rq.setParams(params);
220
- }
263
+ callRpcMethod(shv_path: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string]): Promise<ResultOrError<string>>;
264
+ callRpcMethod(shv_path: string | undefined, method: 'dir', params?: RpcValue): Promise<ResultOrError<DirResult>>;
265
+ callRpcMethod(shv_path: string | undefined, method: 'ls', params?: RpcValue): Promise<ResultOrError<LsResult>>;
266
+ callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue): Promise<ResultOrError>;
267
+ callRpcMethod(shv_path: string | undefined, method: string, params?: RpcValue): Promise<ResultOrError> {
268
+ const rqId = this.requestId++;
269
+ const rq: RpcRequest = new RpcValueWithMetaData(makeMetaMap({
270
+ [RPC_MESSAGE_REQUEST_ID]: rqId,
271
+ [RPC_MESSAGE_METHOD]: method ?? '',
272
+ [RPC_MESSAGE_SHV_PATH]: shv_path ?? '',
273
+ }), makeIMap({
274
+ [RPC_MESSAGE_PARAMS]: params,
275
+ }),
276
+ );
277
+
221
278
  this.sendRpcMessage(rq);
222
279
 
223
- const promise = new Promise<RpcResponse>(resolve => {
224
- this.rpcHandlers[rq_id] = {resolve, timeout_handle: self.setTimeout(() => {
280
+ const promise = new Promise<ResultOrError>(resolve => {
281
+ this.rpcHandlers[rqId] = {resolve, timeout_handle: self.setTimeout(() => {
225
282
  resolve(new MethodCallTimeout(makeIMap({
226
283
  [ERROR_CODE]: ErrorCode.MethodCallTimeout,
227
284
  [ERROR_MESSAGE]: `Shv call timeout after: ${this.timeout} msec.`,
285
+ [ERROR_DATA]: undefined,
228
286
  })));
229
287
  }, this.timeout)};
230
288
  });
@@ -234,22 +292,23 @@ class WsClient {
234
292
 
235
293
  sendRpcMessage(rpc_msg: RpcMessage) {
236
294
  if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
237
- this.logDebug('sending rpc message:', rpc_msg.toCpon());
238
- const msg_data = new Uint8Array(rpc_msg.toChainPack());
295
+ this.logDebug('sending rpc message:', toCpon(rpc_msg));
296
+ const msgData = new Uint8Array(toChainPack(rpc_msg));
239
297
 
240
298
  const wr = new ChainPackWriter();
241
- wr.writeUIntData(msg_data.length + 1);
242
- const dgram = new Uint8Array(wr.ctx.length + 1 + msg_data.length);
299
+ wr.writeUIntData(msgData.length + 1);
300
+ const dgram = new Uint8Array(wr.ctx.length + 1 + msgData.length);
243
301
  let ix = 0;
244
302
  for (let i = 0; i < wr.ctx.length; i++) {
245
303
  dgram[ix++] = wr.ctx.data[i];
246
304
  }
247
305
 
248
- dgram[ix++] = ChainpackProtocolType;
306
+ dgram[ix++] = CHAINPACK_PROTOCOL_TYPE;
249
307
 
250
- for (const msg_datum of msg_data) {
251
- dgram[ix++] = msg_datum;
308
+ for (const msgDatum of msgData) {
309
+ dgram[ix++] = msgDatum;
252
310
  }
311
+
253
312
  this.logDebug(`sending ${dgram.length} bytes of data`);
254
313
  this.websocket.send(dgram.buffer);
255
314
  }
@@ -260,6 +319,7 @@ class WsClient {
260
319
  this.logDebug(`Already subscribed {$path}:${method} for subscriber ${subscriber}`);
261
320
  return;
262
321
  }
322
+
263
323
  // If this path:method has not been subscribed on the broker, do it now
264
324
  if (!this.subscriptions.some(val => val.path === path && val.method === method)) {
265
325
  this.callRpcMethod('.broker/app', 'subscribe', makeMap({
@@ -268,6 +328,7 @@ class WsClient {
268
328
  this.logDebug(`Couldn't subscribe to ${path}, ${method}`);
269
329
  });
270
330
  }
331
+
271
332
  this.subscriptions.push({
272
333
  subscriber,
273
334
  path,
@@ -282,11 +343,13 @@ class WsClient {
282
343
  this.logDebug(`No such subscription ${path}:${method} for subscriber ${subscriber}`);
283
344
  return;
284
345
  }
346
+
285
347
  this.subscriptions.splice(idx, 1);
286
348
  // Unsubscribe on the broker only if there are no other subscriptions of this path:method
287
349
  if (this.subscriptions.some(val => val.path === path && val.method === method)) {
288
350
  return;
289
351
  }
352
+
290
353
  this.callRpcMethod('.broker/app', 'unsubscribe', makeMap({
291
354
  method, path,
292
355
  })).catch(() => {
package/src/zod.ts CHANGED
@@ -1,11 +1,38 @@
1
1
  import {z, type ZodType} from 'zod';
2
- import {type IMap, isIMap, isShvMap, type RpcValue, type ShvMap, UInt} from './rpcvalue';
2
+ import {Decimal, Double, type IMap, isIMap, isMetaMap, isShvMap, type MetaMap, type RpcValue, type RpcValueType, RpcValueWithMetaData, type ShvMap, UInt} from './rpcvalue';
3
3
 
4
- export const map = <T extends Record<string, ZodType<any, any, any>>>(schema: T) => z.custom<ShvMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isShvMap(data) && z.object(schema).safeParse(data).success);
4
+ export const map = <T extends Record<string, ZodType<any, any, any>>>(schema?: T) => z.custom<ShvMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isShvMap(data) && (schema === undefined || z.object(schema).safeParse(data).success));
5
5
  export const recmap = <T extends ZodType<any, any, any>>(schema: T) => z.custom<ShvMap<Record<string, z.infer<T>>>>((data: RpcValue) => isShvMap(data) && z.record(z.string(), schema).safeParse(data).success);
6
- export const imap = <T extends Record<string, ZodType<any, any, any>>>(schema: T) => z.custom<IMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isIMap(data) && z.object(schema).safeParse(data).success);
6
+ export const imap = <T extends Record<string, ZodType<any, any, any>>>(schema?: T) => z.custom<IMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isIMap(data) && (schema === undefined || z.object(schema).safeParse(data).success));
7
+ export const metamap = <T extends Record<string | number, ZodType<any, any, any>>>(schema?: T) => z.custom<MetaMap<{[K in keyof T]: z.infer<T[K]>}>>((data: RpcValue) => isMetaMap(data) && (schema === undefined || z.object(schema).safeParse(data).success));
7
8
  export const int = () => z.number();
8
9
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments -- Zod needs the default argument, otherwise it'll infer as UInt<unknown>
9
10
  export const uint = () => z.instanceof(UInt<number>);
11
+ export const double = () => z.instanceof(Double);
12
+ export const decimal = () => z.instanceof(Decimal);
13
+ export const blob = () => z.instanceof(ArrayBuffer);
14
+
15
+ const withMetaInstanceParser = z.instanceof(RpcValueWithMetaData);
16
+ export const rpcvalue = () => z.union([
17
+ z.undefined(),
18
+ z.boolean(),
19
+ z.number(),
20
+ uint(),
21
+ double(),
22
+ decimal(),
23
+ blob(),
24
+ z.string(),
25
+ z.date(),
26
+ z.array(z.any()),
27
+ map(),
28
+ imap(),
29
+ withMetaInstanceParser,
30
+ ]);
31
+
32
+ export const withMeta = <MetaSchema extends MetaMap, ValueSchema extends RpcValueType>(metaParser: ZodType<MetaSchema, any, any>, valueParser: ZodType<ValueSchema, any, any>) =>
33
+ z.custom<RpcValueWithMetaData<z.infer<typeof metaParser>, z.infer<typeof valueParser>>>((data: any) => withMetaInstanceParser.and(z.object({
34
+ meta: metaParser,
35
+ value: valueParser,
36
+ })).safeParse(data).success);
10
37
 
11
38
  export * from 'zod';
package/test.ts CHANGED
@@ -7,6 +7,7 @@ const checkEq = (e1: string | number, e2: string | number) => {
7
7
  throw new Error(`test check error: ${e1} === ${e2}`);
8
8
  }
9
9
  };
10
+
10
11
  for (const lst of [
11
12
  [((2 ** 31) - 1) + 'u', null],
12
13
  [((2 ** 32) - 1) + 'u', null], // too big for JS bitwise operations
@@ -78,6 +79,7 @@ for (const lst of [
78
79
  const cpn2 = toCpon(rv2);
79
80
  checkEq(cpn1, cpn2);
80
81
  }
82
+
81
83
  // same points in time
82
84
  const v1 = fromCpon('d"2017-05-03T18:30:00Z"') as DateTime;
83
85
  const v2 = fromCpon('d"2017-05-03T22:30:00+04"') as DateTime;