@c9up/comet 0.1.1 → 0.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAGN,QAAQ,EAGR,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAC1B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAC7B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAChC,wDAAwD;IACxD,SAAS,EAAE,YAAY,CAAC;IACxB,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IAC1C;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,CAAC,CAAC;IAC7B,uFAAuF;IACvF,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,wFAAwF;AACxF,MAAM,WAAW,OAAO,CAAC,CAAC,GAAG,OAAO;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,CAAC,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,OAAO,IAC9B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACtB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAC;AAElC,MAAM,WAAW,SAAS;IACzB;;;;;OAKG;IACH,IAAI,CAAC,CAAC,GAAG,OAAO,EACf,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAAC;IACd;;;OAGG;IACH,KAAK,CACJ,KAAK,EAAE,OAAO,EAAE,EAChB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAChC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAgFpE"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAGN,QAAQ,EAGR,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAC1B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAC7B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAChC,wDAAwD;IACxD,SAAS,EAAE,YAAY,CAAC;IACxB,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IAC1C;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,CAAC,CAAC;IAC7B,uFAAuF;IACvF,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,wFAAwF;AACxF,MAAM,WAAW,OAAO,CAAC,CAAC,GAAG,OAAO;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,CAAC,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,OAAO,IAC9B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACtB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAC;AAElC,MAAM,WAAW,SAAS;IACzB;;;;;OAKG;IACH,IAAI,CAAC,CAAC,GAAG,OAAO,EACf,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAAC;IACd;;;OAGG;IACH,KAAK,CACJ,KAAK,EAAE,OAAO,EAAE,EAChB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAChC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAyHpE"}
package/dist/client.js CHANGED
@@ -28,11 +28,20 @@ export function createRpcClient(options) {
28
28
  if (!isObject(res) || res.jsonrpc !== "2.0" || res.id !== id) {
29
29
  throw new RpcError(RpcErrorCode.InternalError, `Malformed or mismatched JSON-RPC response for "${method}" (bad jsonrpc/id envelope)`);
30
30
  }
31
+ // §5: `result` and `error` are mutually exclusive — "Either the
32
+ // result member or error member MUST be included, but both members
33
+ // MUST NOT be included." A response carrying both is malformed, and
34
+ // reading the error out of it is guessing at which half the sender
35
+ // meant. Refused, the way a bad jsonrpc/id envelope already is.
36
+ const hasResult = "result" in res;
37
+ if (hasResult && res.error !== undefined) {
38
+ throw new RpcError(RpcErrorCode.InternalError, `Malformed JSON-RPC response for "${method}" (carries both result and error)`);
39
+ }
31
40
  if (res.error !== undefined)
32
41
  throw toRpcError(res.error);
33
42
  // A conformant success response carries `result` (any JSON value,
34
43
  // including null) and no error — neither key present is malformed.
35
- if (!("result" in res)) {
44
+ if (!hasResult) {
36
45
  throw new RpcError(RpcErrorCode.InternalError, `JSON-RPC response for "${method}" has neither result nor error`);
37
46
  }
38
47
  // Result boundary — the same unchecked `T` assertion HTTP clients use,
@@ -53,21 +62,48 @@ export function createRpcClient(options) {
53
62
  if (!Array.isArray(res)) {
54
63
  throw new RpcError(RpcErrorCode.InternalError, "Malformed JSON-RPC batch response");
55
64
  }
65
+ // Envelope conformance, per item, exactly as `call` applies it — this
66
+ // checked none of it, so `{ jsonrpc: "1.0", id: 0 }` came back as
67
+ // `{ ok: true, value: undefined }` where the single-call path would
68
+ // have refused it.
56
69
  const byId = new Map();
57
- for (const item of res)
58
- if (isObject(item))
59
- byId.set(item.id, item);
70
+ const duplicated = new Set();
71
+ for (const item of res) {
72
+ if (!isObject(item))
73
+ continue;
74
+ // A repeated id is a malformed batch, and silently keeping the last
75
+ // one lets a response answer a call it was not for.
76
+ if (byId.has(item.id))
77
+ duplicated.add(item.id);
78
+ byId.set(item.id, item);
79
+ }
60
80
  return calls.map((c, index) => {
81
+ const fail = (message) => ({
82
+ ok: false,
83
+ error: new RpcError(RpcErrorCode.InternalError, message),
84
+ });
61
85
  const envelope = byId.get(index);
62
- if (!envelope) {
63
- return {
64
- ok: false,
65
- error: new RpcError(RpcErrorCode.InternalError, `No response for "${c.method}"`),
66
- };
86
+ if (!envelope)
87
+ return fail(`No response for "${c.method}"`);
88
+ if (duplicated.has(index)) {
89
+ return fail(`More than one response carried id ${index}`);
90
+ }
91
+ if (envelope.jsonrpc !== "2.0") {
92
+ return fail(`Malformed JSON-RPC response for "${c.method}" (bad jsonrpc version)`);
93
+ }
94
+ const hasResult = "result" in envelope;
95
+ // §5: mutually exclusive. See the single-call path.
96
+ if (hasResult && envelope.error !== undefined) {
97
+ return fail(`Malformed JSON-RPC response for "${c.method}" (carries both result and error)`);
67
98
  }
68
99
  if (envelope.error !== undefined) {
69
100
  return { ok: false, error: toRpcError(envelope.error) };
70
101
  }
102
+ // A conformant success carries `result` — any JSON value, null
103
+ // included — so its absence is malformed, not an undefined value.
104
+ if (!hasResult) {
105
+ return fail(`JSON-RPC response for "${c.method}" has neither result nor error`);
106
+ }
71
107
  const value = c.parse ? c.parse(envelope.result) : envelope.result;
72
108
  return { ok: true, value };
73
109
  });
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACN,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,UAAU,GACV,MAAM,eAAe,CAAC;AAgEvB,MAAM,UAAU,eAAe,CAAC,OAAyB;IACxD,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC;IAClC,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,OAAO;QACN,KAAK,CAAC,IAAI,CACT,MAAc,EACd,MAAgB,EAChB,WAA+B;YAE/B,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC;YACpB,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE;gBAClE,MAAM,EAAE,WAAW,EAAE,MAAM;aAC3B,CAAC,CAAC;YACH,yEAAyE;YACzE,mEAAmE;YACnE,uEAAuE;YACvE,YAAY;YACZ,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,KAAK,KAAK,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC9D,MAAM,IAAI,QAAQ,CACjB,YAAY,CAAC,aAAa,EAC1B,kDAAkD,MAAM,6BAA6B,CACrF,CAAC;YACH,CAAC;YACD,gEAAgE;YAChE,mEAAmE;YACnE,oEAAoE;YACpE,mEAAmE;YACnE,gEAAgE;YAChE,MAAM,SAAS,GAAG,QAAQ,IAAI,GAAG,CAAC;YAClC,IAAI,SAAS,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC1C,MAAM,IAAI,QAAQ,CACjB,YAAY,CAAC,aAAa,EAC1B,oCAAoC,MAAM,mCAAmC,CAC7E,CAAC;YACH,CAAC;YACD,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS;gBAAE,MAAM,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACzD,kEAAkE;YAClE,mEAAmE;YACnE,IAAI,CAAC,SAAS,EAAE,CAAC;gBAChB,MAAM,IAAI,QAAQ,CACjB,YAAY,CAAC,aAAa,EAC1B,0BAA0B,MAAM,gCAAgC,CAChE,CAAC;YACH,CAAC;YACD,uEAAuE;YACvE,iEAAiE;YACjE,OAAO,WAAW,EAAE,KAAK;gBACxB,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;gBAC/B,CAAC,CAAE,GAAG,CAAC,MAAY,CAAC;QACtB,CAAC;QAED,KAAK,CAAC,KAAK,CACV,KAAgB,EAChB,YAAuC;YAEvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;YACvC,6DAA6D;YAC7D,YAAY,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CACvC,CAAC;YACF,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE;gBAC1C,MAAM,EAAE,YAAY,EAAE,MAAM;aAC5B,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,QAAQ,CACjB,YAAY,CAAC,aAAa,EAC1B,mCAAmC,CACnC,CAAC;YACH,CAAC;YACD,sEAAsE;YACtE,kEAAkE;YAClE,oEAAoE;YACpE,mBAAmB;YACnB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAoC,CAAC;YACzD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAW,CAAC;YACtC,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;gBACxB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAC9B,oEAAoE;gBACpE,oDAAoD;gBACpD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC/C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACzB,CAAC;YACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;gBAC7B,MAAM,IAAI,GAAG,CAAC,OAAe,EAAa,EAAE,CAAC,CAAC;oBAC7C,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE,IAAI,QAAQ,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC;iBACxD,CAAC,CAAC;gBACH,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACjC,IAAI,CAAC,QAAQ;oBAAE,OAAO,IAAI,CAAC,oBAAoB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;gBAC5D,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC3B,OAAO,IAAI,CAAC,qCAAqC,KAAK,EAAE,CAAC,CAAC;gBAC3D,CAAC;gBACD,IAAI,QAAQ,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;oBAChC,OAAO,IAAI,CACV,oCAAoC,CAAC,CAAC,MAAM,yBAAyB,CACrE,CAAC;gBACH,CAAC;gBACD,MAAM,SAAS,GAAG,QAAQ,IAAI,QAAQ,CAAC;gBACvC,oDAAoD;gBACpD,IAAI,SAAS,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;oBAC/C,OAAO,IAAI,CACV,oCAAoC,CAAC,CAAC,MAAM,mCAAmC,CAC/E,CAAC;gBACH,CAAC;gBACD,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;oBAClC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzD,CAAC;gBACD,+DAA+D;gBAC/D,kEAAkE;gBAClE,IAAI,CAAC,SAAS,EAAE,CAAC;oBAChB,OAAO,IAAI,CACV,0BAA0B,CAAC,CAAC,MAAM,gCAAgC,CAClE,CAAC;gBACH,CAAC;gBACD,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACnE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YAC5B,CAAC,CAAC,CAAC;QACJ,CAAC;KACD,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/comet",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Comet — agnostic JSON-RPC 2.0 protocol + isomorphic, transport-injectable client for the Ream framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/client.ts CHANGED
@@ -105,10 +105,22 @@ export function createRpcClient(options: RpcClientOptions): RpcClient {
105
105
  `Malformed or mismatched JSON-RPC response for "${method}" (bad jsonrpc/id envelope)`,
106
106
  );
107
107
  }
108
+ // §5: `result` and `error` are mutually exclusive — "Either the
109
+ // result member or error member MUST be included, but both members
110
+ // MUST NOT be included." A response carrying both is malformed, and
111
+ // reading the error out of it is guessing at which half the sender
112
+ // meant. Refused, the way a bad jsonrpc/id envelope already is.
113
+ const hasResult = "result" in res;
114
+ if (hasResult && res.error !== undefined) {
115
+ throw new RpcError(
116
+ RpcErrorCode.InternalError,
117
+ `Malformed JSON-RPC response for "${method}" (carries both result and error)`,
118
+ );
119
+ }
108
120
  if (res.error !== undefined) throw toRpcError(res.error);
109
121
  // A conformant success response carries `result` (any JSON value,
110
122
  // including null) and no error — neither key present is malformed.
111
- if (!("result" in res)) {
123
+ if (!hasResult) {
112
124
  throw new RpcError(
113
125
  RpcErrorCode.InternalError,
114
126
  `JSON-RPC response for "${method}" has neither result nor error`,
@@ -139,22 +151,51 @@ export function createRpcClient(options: RpcClientOptions): RpcClient {
139
151
  "Malformed JSON-RPC batch response",
140
152
  );
141
153
  }
154
+ // Envelope conformance, per item, exactly as `call` applies it — this
155
+ // checked none of it, so `{ jsonrpc: "1.0", id: 0 }` came back as
156
+ // `{ ok: true, value: undefined }` where the single-call path would
157
+ // have refused it.
142
158
  const byId = new Map<unknown, Record<string, unknown>>();
143
- for (const item of res) if (isObject(item)) byId.set(item.id, item);
159
+ const duplicated = new Set<unknown>();
160
+ for (const item of res) {
161
+ if (!isObject(item)) continue;
162
+ // A repeated id is a malformed batch, and silently keeping the last
163
+ // one lets a response answer a call it was not for.
164
+ if (byId.has(item.id)) duplicated.add(item.id);
165
+ byId.set(item.id, item);
166
+ }
144
167
  return calls.map((c, index) => {
168
+ const fail = (message: string): RpcResult => ({
169
+ ok: false,
170
+ error: new RpcError(RpcErrorCode.InternalError, message),
171
+ });
145
172
  const envelope = byId.get(index);
146
- if (!envelope) {
147
- return {
148
- ok: false,
149
- error: new RpcError(
150
- RpcErrorCode.InternalError,
151
- `No response for "${c.method}"`,
152
- ),
153
- };
173
+ if (!envelope) return fail(`No response for "${c.method}"`);
174
+ if (duplicated.has(index)) {
175
+ return fail(`More than one response carried id ${index}`);
176
+ }
177
+ if (envelope.jsonrpc !== "2.0") {
178
+ return fail(
179
+ `Malformed JSON-RPC response for "${c.method}" (bad jsonrpc version)`,
180
+ );
181
+ }
182
+ const hasResult = "result" in envelope;
183
+ // §5: mutually exclusive. See the single-call path.
184
+ if (hasResult && envelope.error !== undefined) {
185
+ return fail(
186
+ `Malformed JSON-RPC response for "${c.method}" (carries both result and error)`,
187
+ );
154
188
  }
155
189
  if (envelope.error !== undefined) {
156
190
  return { ok: false, error: toRpcError(envelope.error) };
157
191
  }
192
+ // A conformant success carries `result` — any JSON value, null
193
+ // included — so its absence is malformed, not an undefined value.
194
+ if (!hasResult) {
195
+ return fail(
196
+ `JSON-RPC response for "${c.method}" has neither result nor error`,
197
+ );
198
+ }
158
199
  const value = c.parse ? c.parse(envelope.result) : envelope.result;
159
200
  return { ok: true, value };
160
201
  });