@covenant-rpc/client 0.2.0 → 0.4.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.
@@ -0,0 +1,49 @@
1
+ import type { ChannelMap, Covenant, ProcedureMap } from "@covenant-rpc/core";
2
+ import type { InferChannelConnectionRequest, InferChannelParams, InferChannelServerMessage, InferChannelClientMessage } from "@covenant-rpc/core/channel";
3
+ import type { ClientToServerConnection, ClientToSidekickConnection } from "@covenant-rpc/core/interfaces";
4
+ import type { InferProcedureInputs, InferProcedureResult } from "@covenant-rpc/core/procedure";
5
+ export type MutationKey<P extends ProcedureMap> = {
6
+ [k in keyof P]: P[k]["type"] extends "mutation" ? k : never;
7
+ }[keyof P];
8
+ export type QueryKey<P extends ProcedureMap> = {
9
+ [k in keyof P]: P[k]["type"] extends "query" ? k : never;
10
+ }[keyof P];
11
+ export type Listener<T> = (s: T) => void | Promise<void>;
12
+ type ChannelConnectionResult<E> = {
13
+ success: true;
14
+ token: string;
15
+ error: null;
16
+ } | {
17
+ success: false;
18
+ token: null;
19
+ error: E;
20
+ };
21
+ export declare class CovenantClient<P extends ProcedureMap, C extends ChannelMap> {
22
+ private covenant;
23
+ private serverConnection;
24
+ private sidekickConnection;
25
+ private listeners;
26
+ private remoteListenersCount;
27
+ private channelSubscriptions;
28
+ private pendingSendPromises;
29
+ private sendCounter;
30
+ constructor(covenant: Covenant<P, C>, { serverConnection, sidekickConnection }: {
31
+ serverConnection: ClientToServerConnection;
32
+ sidekickConnection: ClientToSidekickConnection;
33
+ });
34
+ private call;
35
+ private refetchResources;
36
+ query<K extends QueryKey<P>>(procedure: K, inputs: InferProcedureInputs<P[K]>): Promise<InferProcedureResult<P[K]>>;
37
+ mutate<K extends MutationKey<P>>(procedure: K, inputs: InferProcedureInputs<P[K]>): Promise<InferProcedureResult<P[K]>>;
38
+ connect<K extends keyof C>(channelName: K, params: InferChannelParams<C[K]>, data: InferChannelConnectionRequest<C[K]>): Promise<ChannelConnectionResult<any>>;
39
+ send<K extends keyof C>(channelName: K, params: InferChannelParams<C[K]>, token: string, data: InferChannelClientMessage<C[K]>): Promise<void>;
40
+ subscribe<K extends keyof C>(channelName: K, params: InferChannelParams<C[K]>, token: string, callback: Listener<InferChannelServerMessage<C[K]>>): Promise<() => void>;
41
+ listen<K extends QueryKey<P>>(procedure: K, inputs: InferProcedureInputs<P[K]>, callback: Listener<InferProcedureResult<P[K]>>, remote?: boolean): () => void;
42
+ private addListener;
43
+ private removeListener;
44
+ private increaseRemoteCount;
45
+ private decreateRemoteCount;
46
+ private cleanRemoteListeners;
47
+ }
48
+ export {};
49
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"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"}
package/dist/client.js ADDED
@@ -0,0 +1,273 @@
1
+ import { issuesToString } from "@covenant-rpc/core/utils";
2
+ function getChannelKey(channel, params) {
3
+ const paramStr = Object.keys(params)
4
+ .sort()
5
+ .map(k => `${k}:${params[k]}`)
6
+ .join(",");
7
+ return `${channel}/${paramStr}`;
8
+ }
9
+ export class CovenantClient {
10
+ covenant;
11
+ serverConnection;
12
+ sidekickConnection;
13
+ listeners = new Map();
14
+ remoteListenersCount = new Map();
15
+ channelSubscriptions = new Map();
16
+ pendingSendPromises = new Map();
17
+ sendCounter = 0;
18
+ constructor(covenant, { serverConnection, sidekickConnection }) {
19
+ this.covenant = covenant;
20
+ this.serverConnection = serverConnection;
21
+ this.sidekickConnection = sidekickConnection;
22
+ this.sidekickConnection.onMessage(async (message) => {
23
+ if (message.type === "message") {
24
+ // Route message to channel subscribers
25
+ const key = getChannelKey(message.channel, message.params);
26
+ const callbacks = this.channelSubscriptions.get(key) || [];
27
+ for (const callback of callbacks) {
28
+ await callback(message.data);
29
+ }
30
+ }
31
+ else if (message.type === "updated") {
32
+ // Handle resource updates
33
+ await this.refetchResources([message.resource]);
34
+ }
35
+ else if (message.type === "error") {
36
+ // Handle errors - resolve any pending send promises with error
37
+ const pendingKeys = Array.from(this.pendingSendPromises.keys());
38
+ for (const key of pendingKeys) {
39
+ const pending = this.pendingSendPromises.get(key);
40
+ if (pending) {
41
+ this.pendingSendPromises.delete(key);
42
+ pending.reject(new Error(message.error.message));
43
+ }
44
+ }
45
+ }
46
+ });
47
+ }
48
+ async call(procedure, inputs) {
49
+ const result = await this.serverConnection.runProcedure({
50
+ procedure: String(procedure),
51
+ inputs: inputs,
52
+ });
53
+ if (result.status === "ERR") {
54
+ return {
55
+ success: false,
56
+ data: null,
57
+ resources: null,
58
+ error: result.error,
59
+ };
60
+ }
61
+ const declaration = this.covenant.procedures[procedure];
62
+ const validation = await declaration.output["~standard"].validate(result.data);
63
+ if (validation.issues) {
64
+ return {
65
+ success: false,
66
+ data: null,
67
+ resources: null,
68
+ error: {
69
+ code: 500,
70
+ message: `Improper response from server from procedure ${String(procedure)}: ${issuesToString(validation.issues)}`,
71
+ }
72
+ };
73
+ }
74
+ return {
75
+ success: true,
76
+ error: null,
77
+ // typescript isn't smart enough here but we still love it
78
+ data: validation.value,
79
+ resources: result.resources,
80
+ };
81
+ }
82
+ async refetchResources(resources) {
83
+ const s = new Set(resources);
84
+ const neededListeners = this.listeners.keys().filter(k => s.has(k));
85
+ const functions = [];
86
+ for (const l of neededListeners) {
87
+ functions.push(...(this.listeners.get(l)));
88
+ }
89
+ await Promise.all(functions.map(f => f()));
90
+ }
91
+ async query(procedure, inputs) {
92
+ return await this.call(procedure, inputs);
93
+ }
94
+ async mutate(procedure, inputs) {
95
+ const result = await this.call(procedure, inputs);
96
+ if (result.success === false) {
97
+ return result;
98
+ }
99
+ await this.refetchResources(result.resources);
100
+ return result;
101
+ }
102
+ async connect(channelName, params, data) {
103
+ const response = await this.serverConnection.sendConnectionRequest({
104
+ channel: String(channelName),
105
+ params: params,
106
+ data,
107
+ });
108
+ if (response.result.type !== "OK") {
109
+ return {
110
+ success: false,
111
+ token: null,
112
+ error: response.result.error,
113
+ };
114
+ }
115
+ return {
116
+ success: true,
117
+ token: response.result.token,
118
+ error: null,
119
+ };
120
+ }
121
+ async send(channelName, params, token, data) {
122
+ const sendId = `send-${this.sendCounter++}`;
123
+ return new Promise((resolve, reject) => {
124
+ this.pendingSendPromises.set(sendId, { resolve, reject });
125
+ // Send the message
126
+ this.sidekickConnection.sendMessage({
127
+ type: "send",
128
+ token,
129
+ channel: String(channelName),
130
+ params: params,
131
+ data,
132
+ });
133
+ // Set a timeout to auto-resolve if no error comes back
134
+ setTimeout(() => {
135
+ const pending = this.pendingSendPromises.get(sendId);
136
+ if (pending) {
137
+ this.pendingSendPromises.delete(sendId);
138
+ resolve();
139
+ }
140
+ }, 100); // Wait 100ms for potential errors
141
+ });
142
+ }
143
+ async subscribe(channelName, params, token, callback) {
144
+ const key = getChannelKey(String(channelName), params);
145
+ // Add callback to subscriptions
146
+ if (!this.channelSubscriptions.has(key)) {
147
+ this.channelSubscriptions.set(key, []);
148
+ }
149
+ this.channelSubscriptions.get(key).push(callback);
150
+ // Subscribe to channel via sidekick
151
+ this.sidekickConnection.sendMessage({
152
+ type: "subscribe",
153
+ token,
154
+ });
155
+ // Return unsubscribe function
156
+ return () => {
157
+ const callbacks = this.channelSubscriptions.get(key) || [];
158
+ const index = callbacks.indexOf(callback);
159
+ if (index !== -1) {
160
+ callbacks.splice(index, 1);
161
+ }
162
+ if (callbacks.length === 0) {
163
+ this.channelSubscriptions.delete(key);
164
+ }
165
+ // Unsubscribe from channel via sidekick
166
+ this.sidekickConnection.sendMessage({
167
+ type: "unsubscribe",
168
+ token,
169
+ });
170
+ };
171
+ }
172
+ listen(procedure, inputs, callback, remote = false) {
173
+ const listener = async () => {
174
+ const res = await this.call(procedure, inputs);
175
+ callback(res);
176
+ };
177
+ const unsubscribe = () => {
178
+ const removed = this.removeListener(listener);
179
+ if (remote) {
180
+ for (const r of removed) {
181
+ this.decreateRemoteCount(r);
182
+ }
183
+ this.cleanRemoteListeners();
184
+ }
185
+ };
186
+ // we call the promise "in the background" so that this can be
187
+ // a synchronous function
188
+ const p = async () => {
189
+ const res = await this.call(procedure, inputs);
190
+ callback(res);
191
+ // we only add the listener if it's a non-error response. It is
192
+ // up to the library user to ensure that errors are handled
193
+ if (res.error) {
194
+ return;
195
+ }
196
+ this.addListener(res.resources, listener);
197
+ if (remote) {
198
+ this.sidekickConnection.sendMessage({
199
+ type: "listen",
200
+ resources: res.resources,
201
+ });
202
+ for (const r of res.resources) {
203
+ this.increaseRemoteCount(r);
204
+ }
205
+ }
206
+ };
207
+ p();
208
+ return unsubscribe;
209
+ }
210
+ addListener(resources, listener, isRemote = true) {
211
+ // TODO - remote listeners
212
+ for (const r of resources) {
213
+ if (this.listeners.has(r)) {
214
+ const newListeners = [...(this.listeners.get(r))];
215
+ newListeners.push(listener);
216
+ this.listeners.set(r, newListeners);
217
+ }
218
+ else {
219
+ this.listeners.set(r, [listener]);
220
+ }
221
+ }
222
+ }
223
+ // this is how we track which listeners are depending on a remote resource and which
224
+ removeListener(listener) {
225
+ const removed = [];
226
+ for (const r of this.listeners.keys()) {
227
+ const currentListeners = this.listeners.get(r);
228
+ // minor optimization?
229
+ if (currentListeners.find(l => l === listener) === undefined) {
230
+ continue;
231
+ }
232
+ removed.push(r);
233
+ const newListeners = currentListeners.filter(l => l !== listener);
234
+ this.listeners.set(r, newListeners);
235
+ }
236
+ return removed;
237
+ }
238
+ increaseRemoteCount(resource) {
239
+ if (this.remoteListenersCount.has(resource)) {
240
+ const c = this.remoteListenersCount.get(resource);
241
+ this.remoteListenersCount.set(resource, c + 1);
242
+ }
243
+ else {
244
+ this.remoteListenersCount.set(resource, 1);
245
+ }
246
+ }
247
+ decreateRemoteCount(resource) {
248
+ if (this.remoteListenersCount.has(resource)) {
249
+ const c = this.remoteListenersCount.get(resource);
250
+ if (c === 0) {
251
+ throw new Error("Internal covenant error (not your fault unless you are developing covenant): Tried to reduce count for resource already at 0. Please report a bug.");
252
+ }
253
+ this.remoteListenersCount.set(resource, c - 1);
254
+ }
255
+ else {
256
+ throw new Error("Internal covenant error (not your fault unless you are developing covenant): Tried to reduce count for resource with no remote subscriptions. Please report a bug.");
257
+ }
258
+ }
259
+ cleanRemoteListeners() {
260
+ const unlistenTo = [];
261
+ for (const k of this.remoteListenersCount.keys()) {
262
+ const c = this.remoteListenersCount.get(k);
263
+ if (c === 0) {
264
+ unlistenTo.push(k);
265
+ }
266
+ }
267
+ this.sidekickConnection.sendMessage({
268
+ type: "unlisten",
269
+ resources: unlistenTo,
270
+ });
271
+ }
272
+ }
273
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
1
  export { CovenantClient, type MutationKey, type QueryKey } from "./client";
2
2
  export { httpClientToServer, httpClientToSidekick } from "./interfaces/http";
3
3
  export { emptyClientToServer, emptyClientToSidekick } from "./interfaces/empty";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { CovenantClient } from "./client";
2
+ export { httpClientToServer, httpClientToSidekick } from "./interfaces/http";
3
+ export { emptyClientToServer, emptyClientToSidekick } from "./interfaces/empty";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAmC,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ClientToServerConnection, ClientToSidekickConnection } from "@covenant-rpc/core/interfaces";
2
+ export declare function emptyClientToSidekick(): ClientToSidekickConnection;
3
+ export declare function emptyClientToServer(): ClientToServerConnection;
4
+ //# sourceMappingURL=empty.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty.d.ts","sourceRoot":"","sources":["../../interfaces/empty.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE1G,wBAAgB,qBAAqB,IAAI,0BAA0B,CASlE;AAGD,wBAAgB,mBAAmB,IAAI,wBAAwB,CA4B9D"}
@@ -0,0 +1,39 @@
1
+ export function emptyClientToSidekick() {
2
+ return {
3
+ sendMessage() {
4
+ },
5
+ onMessage() {
6
+ return () => { };
7
+ },
8
+ };
9
+ }
10
+ export function emptyClientToServer() {
11
+ return {
12
+ async sendConnectionRequest(r) {
13
+ return {
14
+ channel: r.channel,
15
+ params: r.params,
16
+ result: {
17
+ type: "ERROR",
18
+ error: {
19
+ params: r.params,
20
+ channel: r.channel,
21
+ fault: "client",
22
+ message: "Empty connection always fails.",
23
+ }
24
+ }
25
+ };
26
+ },
27
+ async runProcedure() {
28
+ return {
29
+ status: "ERR",
30
+ error: {
31
+ code: 501,
32
+ message: "Using an empty client to server connection",
33
+ },
34
+ resources: [],
35
+ };
36
+ }
37
+ };
38
+ }
39
+ //# sourceMappingURL=empty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty.js","sourceRoot":"","sources":["../../interfaces/empty.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,WAAW;QAEX,CAAC;QACD,SAAS;YACP,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC;QACnB,CAAC;KACF,CAAA;AACH,CAAC;AAGD,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,KAAK,CAAC,qBAAqB,CAAC,CAAC;YAC3B,OAAO;gBACL,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE;oBACN,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,MAAM,EAAE,CAAC,CAAC,MAAM;wBAChB,OAAO,EAAE,CAAC,CAAC,OAAO;wBAClB,KAAK,EAAE,QAAQ;wBACf,OAAO,EAAE,gCAAgC;qBAC1C;iBACF;aACF,CAAA;QACH,CAAC;QACD,KAAK,CAAC,YAAY;YAChB,OAAO;gBACL,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE;oBACL,IAAI,EAAE,GAAG;oBACT,OAAO,EAAE,4CAA4C;iBACtD;gBACD,SAAS,EAAE,EAAE;aACd,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ClientToServerConnection, ClientToSidekickConnection } from "@covenant-rpc/core/interfaces";
2
+ export declare function httpClientToSidekick(rootUrl: string): ClientToSidekickConnection;
3
+ export declare function httpClientToServer(covenantUrl: string, extraHeaders: Record<string, string>): ClientToServerConnection;
4
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../interfaces/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAS1G,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,CAEhF;AAqHD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,wBAAwB,CAuGtH"}
@@ -0,0 +1,202 @@
1
+ import { procedureResponseSchema } from "@covenant-rpc/core/procedure";
2
+ import { channelConnectionResponseSchema } from "@covenant-rpc/core/channel";
3
+ import { sidekickOutgoingMessageSchema } from "@covenant-rpc/core/sidekick/protocol";
4
+ import { isPromise } from "@covenant-rpc/core/utils";
5
+ import { v } from "@covenant-rpc/core/validation";
6
+ import ION from "@covenant-rpc/ion";
7
+ export function httpClientToSidekick(rootUrl) {
8
+ return new HttpClientToSidekick(rootUrl);
9
+ }
10
+ class HttpClientToSidekick {
11
+ socketUrl;
12
+ listeners = [];
13
+ //@ts-expect-error we define this in the reconnectSocket method which is called by the constructor
14
+ socket;
15
+ ready = false;
16
+ sendQueue = [];
17
+ constructor(rootUrl) {
18
+ const url = new URL(rootUrl);
19
+ const protocol = url.protocol === "https:" ? "wss" : "ws";
20
+ this.socketUrl = `${protocol}://${url.host}/socket`;
21
+ this.reconnectSocket();
22
+ }
23
+ reconnectSocket() {
24
+ this.socket = new WebSocket(this.socketUrl);
25
+ this.ready = false;
26
+ this.socket.onmessage = async (m) => {
27
+ let data;
28
+ try {
29
+ data = typeof m.data === 'string' ? ION.parse(m.data) : m.data;
30
+ }
31
+ catch (e) {
32
+ await this.emitMessage({
33
+ type: "error",
34
+ error: {
35
+ fault: "sidekick",
36
+ message: "Error parsing JSON from sidekick message",
37
+ channel: "unknown",
38
+ params: {},
39
+ }
40
+ });
41
+ return;
42
+ }
43
+ const message = v.parseSafe(data, sidekickOutgoingMessageSchema);
44
+ if (!message) {
45
+ await this.emitMessage({
46
+ type: "error",
47
+ error: {
48
+ fault: "sidekick",
49
+ message: "Error parsing incoming message from sidekick",
50
+ channel: "unknown",
51
+ params: {},
52
+ }
53
+ });
54
+ return;
55
+ }
56
+ await this.emitMessage(message);
57
+ };
58
+ this.socket.onopen = () => {
59
+ this.ready = true;
60
+ for (const m of this.sendQueue) {
61
+ this.socket.send(ION.stringify(m));
62
+ }
63
+ };
64
+ this.socket.onclose = () => {
65
+ this.ready = false;
66
+ };
67
+ this.socket.onerror = async () => {
68
+ this.ready = false;
69
+ await this.emitMessage({
70
+ type: "error",
71
+ error: {
72
+ fault: "client",
73
+ message: "Unknown websocket error",
74
+ channel: "unknown",
75
+ params: {},
76
+ }
77
+ });
78
+ };
79
+ }
80
+ async emitMessage(m) {
81
+ const promises = [];
82
+ for (const l of this.listeners) {
83
+ const p = l(m);
84
+ if (isPromise(p)) {
85
+ promises.push(p);
86
+ }
87
+ }
88
+ await Promise.all(promises);
89
+ }
90
+ sendMessage(message) {
91
+ if (this.ready) {
92
+ const json = ION.stringify(message);
93
+ this.socket.send(json);
94
+ }
95
+ else {
96
+ this.sendQueue.push(message);
97
+ }
98
+ }
99
+ onMessage(handler) {
100
+ this.listeners.push(handler);
101
+ return () => {
102
+ const newListeners = this.listeners.filter(l => l !== handler);
103
+ this.listeners = newListeners;
104
+ };
105
+ }
106
+ }
107
+ export function httpClientToServer(covenantUrl, extraHeaders) {
108
+ const getHeaders = () => {
109
+ const h = new Headers();
110
+ h.set("Content-Type", "application/json");
111
+ for (const k in extraHeaders) {
112
+ h.set(k, extraHeaders[k]);
113
+ }
114
+ return h;
115
+ };
116
+ const getUrl = (type) => {
117
+ const url = new URL(covenantUrl);
118
+ url.searchParams.set("type", type);
119
+ return url.toString();
120
+ };
121
+ return {
122
+ async sendConnectionRequest(body) {
123
+ try {
124
+ const headers = getHeaders();
125
+ const url = getUrl("connect");
126
+ const response = await fetch(url, {
127
+ method: "POST",
128
+ headers,
129
+ body: ION.stringify(body),
130
+ });
131
+ const responseText = await response.text();
132
+ const responseBody = ION.parse(responseText);
133
+ const connectionResponse = v.parseSafe(responseBody, channelConnectionResponseSchema);
134
+ if (connectionResponse === null) {
135
+ return {
136
+ channel: body.channel,
137
+ params: body.params,
138
+ result: {
139
+ type: "ERROR",
140
+ error: {
141
+ channel: body.channel,
142
+ params: body.params,
143
+ fault: "server",
144
+ message: `Bad response from server: ${JSON.stringify(responseBody)}`,
145
+ },
146
+ },
147
+ };
148
+ }
149
+ return connectionResponse;
150
+ }
151
+ catch (e) {
152
+ return {
153
+ channel: body.channel,
154
+ params: body.params,
155
+ result: {
156
+ type: "ERROR",
157
+ error: {
158
+ channel: body.channel,
159
+ params: body.params,
160
+ fault: "server",
161
+ message: `Unknown error connecting to channel: ${e}`,
162
+ },
163
+ },
164
+ };
165
+ }
166
+ },
167
+ async runProcedure(body) {
168
+ try {
169
+ const headers = getHeaders();
170
+ const url = getUrl("procedure");
171
+ const response = await fetch(url, {
172
+ method: "POST",
173
+ headers,
174
+ body: ION.stringify(body),
175
+ });
176
+ const responseText = await response.text();
177
+ const responseBody = ION.parse(responseText);
178
+ const procedureResponse = v.parseSafe(responseBody, procedureResponseSchema);
179
+ if (procedureResponse === null) {
180
+ return {
181
+ status: "ERR",
182
+ error: {
183
+ code: 500,
184
+ message: `Bad response from server: ${responseBody}`,
185
+ }
186
+ };
187
+ }
188
+ return procedureResponse;
189
+ }
190
+ catch (e) {
191
+ return {
192
+ status: "ERR",
193
+ error: {
194
+ code: 400,
195
+ message: `Unknown error fetching from the server: ${e}`,
196
+ }
197
+ };
198
+ }
199
+ }
200
+ };
201
+ }
202
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../interfaces/http.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,20 +1,23 @@
1
1
  {
2
2
  "name": "@covenant-rpc/client",
3
- "module": "lib/index.ts",
4
- "version": "0.2.0",
3
+ "module": "./dist/index.js",
4
+ "version": "0.4.0",
5
5
  "type": "module",
6
+ "scripts": {
7
+ "build": "bun ../../scripts/build-package.ts ."
8
+ },
6
9
  "publishConfig": {
7
10
  "access": "public"
8
11
  },
9
- "exports": {
10
- ".": "./lib/index.ts",
11
- "./*": "./lib/*.ts"
12
- },
13
12
  "peerDependencies": {
14
13
  "typescript": "^5"
15
14
  },
16
15
  "dependencies": {
17
- "@covenant-rpc/core": "^0.2.0",
18
- "@covenant-rpc/ion": "^1.0.4"
19
- }
16
+ "@covenant-rpc/core": "^0.4.0",
17
+ "@covenant-rpc/ion": "^1.2.0"
18
+ },
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ]
20
23
  }
package/CHANGELOG.md DELETED
@@ -1,16 +0,0 @@
1
- # @covenant-rpc/client
2
-
3
- ## 0.1.3
4
-
5
- ### Patch Changes
6
-
7
- - 8061179: Initial publish
8
- - Updated dependencies [8061179]
9
- - @covenant-rpc/core@0.1.3
10
- - @covenant-rpc/ion@1.0.3
11
-
12
- ## 0.1.2
13
-
14
- ### Patch Changes
15
-
16
- - Fix all packages
package/lib/client.ts DELETED
@@ -1,362 +0,0 @@
1
- import type { ChannelMap, Covenant, ProcedureMap } from "@covenant-rpc/core";
2
- import type { InferChannelConnectionContext, InferChannelConnectionRequest, InferChannelParams, InferChannelServerMessage, InferChannelClientMessage } from "@covenant-rpc/core/channel";
3
- import type { ClientToServerConnection, ClientToSidekickConnection } from "@covenant-rpc/core/interfaces";
4
- import type { InferProcedureInputs, InferProcedureOutputs, InferProcedureResult } from "@covenant-rpc/core/procedure";
5
- import { issuesToString } from "@covenant-rpc/core/utils";
6
-
7
- export type MutationKey<P extends ProcedureMap> = { [k in keyof P]: P[k]["type"] extends "mutation" ? k : never }[keyof P]
8
- export type QueryKey<P extends ProcedureMap> = {
9
- [k in keyof P]: P[k]["type"] extends "query" ? k : never
10
- }[keyof P]
11
-
12
- export type Listener<T> = (s: T) => void | Promise<void>;
13
-
14
- type ChannelConnectionResult<E> =
15
- | { success: true; token: string; error: null }
16
- | { success: false; token: null; error: E };
17
-
18
- function getChannelKey(channel: string, params: Record<string, string>): string {
19
- const paramStr = Object.keys(params)
20
- .sort()
21
- .map(k => `${k}:${params[k]}`)
22
- .join(",");
23
- return `${channel}/${paramStr}`;
24
- }
25
-
26
- export class CovenantClient<
27
- P extends ProcedureMap,
28
- C extends ChannelMap,
29
- > {
30
- private covenant: Covenant<P, C>;
31
- private serverConnection: ClientToServerConnection;
32
- private sidekickConnection: ClientToSidekickConnection;
33
- private listeners: Map<string, (() => Promise<void>)[]> = new Map();
34
- private remoteListenersCount: Map<string, number> = new Map();
35
- private channelSubscriptions: Map<string, Listener<any>[]> = new Map();
36
- private pendingSendPromises: Map<string, { resolve: () => void; reject: (error: Error) => void }> = new Map();
37
- private sendCounter: number = 0;
38
-
39
- constructor(
40
- covenant: Covenant<P, C>,
41
- { serverConnection, sidekickConnection }: {
42
- serverConnection: ClientToServerConnection,
43
- sidekickConnection: ClientToSidekickConnection
44
- }
45
- ) {
46
- this.covenant = covenant;
47
- this.serverConnection = serverConnection;
48
- this.sidekickConnection = sidekickConnection;
49
-
50
- this.sidekickConnection.onMessage(async (message) => {
51
- if (message.type === "message") {
52
- // Route message to channel subscribers
53
- const key = getChannelKey(message.channel, message.params);
54
- const callbacks = this.channelSubscriptions.get(key) || [];
55
- for (const callback of callbacks) {
56
- await callback(message.data);
57
- }
58
- } else if (message.type === "updated") {
59
- // Handle resource updates
60
- await this.refetchResources([message.resource]);
61
- } else if (message.type === "error") {
62
- // Handle errors - resolve any pending send promises with error
63
- const pendingKeys = Array.from(this.pendingSendPromises.keys());
64
- for (const key of pendingKeys) {
65
- const pending = this.pendingSendPromises.get(key);
66
- if (pending) {
67
- this.pendingSendPromises.delete(key);
68
- pending.reject(new Error(message.error.message));
69
- }
70
- }
71
- }
72
- })
73
- }
74
-
75
-
76
- private async call<K extends keyof P>(
77
- procedure: K,
78
- inputs: InferProcedureInputs<P[K]>
79
- ): Promise<InferProcedureResult<P[K]>> {
80
- const result = await this.serverConnection.runProcedure({
81
- procedure: String(procedure),
82
- inputs: inputs,
83
- });
84
-
85
- if (result.status === "ERR") {
86
- return {
87
- success: false,
88
- data: null,
89
- resources: null,
90
- error: result.error,
91
- }
92
- }
93
-
94
- const declaration = this.covenant.procedures[procedure]!;
95
- const validation = await declaration.output["~standard"].validate(result.data);
96
-
97
- if (validation.issues) {
98
- return {
99
- success: false,
100
- data: null,
101
- resources: null,
102
- error: {
103
- code: 500,
104
- message: `Improper response from server from procedure ${String(procedure)}: ${issuesToString(validation.issues)}`,
105
- }
106
- }
107
- }
108
-
109
- return {
110
- success: true,
111
- error: null,
112
- // typescript isn't smart enough here but we still love it
113
- data: validation.value as InferProcedureOutputs<P[K]>,
114
- resources: result.resources,
115
- }
116
- }
117
-
118
- private async refetchResources(resources: string[]) {
119
- const s = new Set(resources);
120
- const neededListeners = this.listeners.keys().filter(k => s.has(k));
121
- const functions: (() => Promise<void>)[] = [];
122
-
123
- for (const l of neededListeners) {
124
- functions.push(...(this.listeners.get(l)!));
125
- }
126
-
127
- await Promise.all(functions.map(f => f()));
128
- }
129
-
130
- async query<K extends QueryKey<P>>(procedure: K, inputs: InferProcedureInputs<P[K]>) {
131
- return await this.call(procedure, inputs);
132
- }
133
-
134
- async mutate<K extends MutationKey<P>>(
135
- procedure: K,
136
- inputs: InferProcedureInputs<P[K]>
137
- ): Promise<InferProcedureResult<P[K]>> {
138
- const result = await this.call(procedure, inputs);
139
-
140
- if (result.success === false) {
141
- return result;
142
- }
143
-
144
- await this.refetchResources(result.resources);
145
-
146
- return result;
147
- }
148
-
149
- async connect<K extends keyof C>(
150
- channelName: K,
151
- params: InferChannelParams<C[K]>,
152
- data: InferChannelConnectionRequest<C[K]>
153
- ): Promise<ChannelConnectionResult<any>> {
154
- const response = await this.serverConnection.sendConnectionRequest({
155
- channel: String(channelName),
156
- params: params as Record<string, string>,
157
- data,
158
- });
159
-
160
- if (response.result.type !== "OK") {
161
- return {
162
- success: false,
163
- token: null,
164
- error: response.result.error,
165
- };
166
- }
167
-
168
- return {
169
- success: true,
170
- token: response.result.token,
171
- error: null,
172
- };
173
- }
174
-
175
- async send<K extends keyof C>(
176
- channelName: K,
177
- params: InferChannelParams<C[K]>,
178
- token: string,
179
- data: InferChannelClientMessage<C[K]>
180
- ): Promise<void> {
181
- const sendId = `send-${this.sendCounter++}`;
182
-
183
- return new Promise<void>((resolve, reject) => {
184
- this.pendingSendPromises.set(sendId, { resolve, reject });
185
-
186
- // Send the message
187
- this.sidekickConnection.sendMessage({
188
- type: "send",
189
- token,
190
- channel: String(channelName),
191
- params: params as Record<string, string>,
192
- data,
193
- });
194
-
195
- // Set a timeout to auto-resolve if no error comes back
196
- setTimeout(() => {
197
- const pending = this.pendingSendPromises.get(sendId);
198
- if (pending) {
199
- this.pendingSendPromises.delete(sendId);
200
- resolve();
201
- }
202
- }, 100); // Wait 100ms for potential errors
203
- });
204
- }
205
-
206
- async subscribe<K extends keyof C>(
207
- channelName: K,
208
- params: InferChannelParams<C[K]>,
209
- token: string,
210
- callback: Listener<InferChannelServerMessage<C[K]>>
211
- ): Promise<() => void> {
212
- const key = getChannelKey(String(channelName), params as Record<string, string>);
213
-
214
- // Add callback to subscriptions
215
- if (!this.channelSubscriptions.has(key)) {
216
- this.channelSubscriptions.set(key, []);
217
- }
218
- this.channelSubscriptions.get(key)!.push(callback);
219
-
220
- // Subscribe to channel via sidekick
221
- this.sidekickConnection.sendMessage({
222
- type: "subscribe",
223
- token,
224
- });
225
-
226
- // Return unsubscribe function
227
- return () => {
228
- const callbacks = this.channelSubscriptions.get(key) || [];
229
- const index = callbacks.indexOf(callback);
230
- if (index !== -1) {
231
- callbacks.splice(index, 1);
232
- }
233
- if (callbacks.length === 0) {
234
- this.channelSubscriptions.delete(key);
235
- }
236
-
237
- // Unsubscribe from channel via sidekick
238
- this.sidekickConnection.sendMessage({
239
- type: "unsubscribe",
240
- token,
241
- });
242
- };
243
- }
244
-
245
-
246
- listen<K extends QueryKey<P>>(
247
- procedure: K,
248
- inputs: InferProcedureInputs<P[K]>,
249
- callback: Listener<InferProcedureResult<P[K]>>,
250
- remote: boolean = false,
251
- ): () => void {
252
- const listener = async () => {
253
- const res = await this.call(procedure, inputs);
254
- callback(res);
255
- }
256
-
257
- const unsubscribe = () => {
258
- const removed = this.removeListener(listener);
259
- if (remote) {
260
- for (const r of removed) {
261
- this.decreateRemoteCount(r);
262
- }
263
- this.cleanRemoteListeners();
264
- }
265
- }
266
-
267
- // we call the promise "in the background" so that this can be
268
- // a synchronous function
269
- const p = async () => {
270
- const res = await this.call(procedure, inputs);
271
- callback(res);
272
-
273
- // we only add the listener if it's a non-error response. It is
274
- // up to the library user to ensure that errors are handled
275
- if (res.error) {
276
- return;
277
- }
278
-
279
- this.addListener(res.resources, listener);
280
- if (remote) {
281
- this.sidekickConnection.sendMessage({
282
- type: "listen",
283
- resources: res.resources,
284
- });
285
- for (const r of res.resources) {
286
- this.increaseRemoteCount(r);
287
- }
288
- }
289
- }
290
- p();
291
-
292
- return unsubscribe;
293
- }
294
-
295
- private addListener(resources: string[], listener: () => Promise<void>, isRemote: boolean = true) {
296
- // TODO - remote listeners
297
- for (const r of resources) {
298
- if (this.listeners.has(r)) {
299
- const newListeners = [...(this.listeners.get(r)!)];
300
- newListeners.push(listener);
301
- this.listeners.set(r, newListeners);
302
- } else {
303
- this.listeners.set(r, [listener]);
304
- }
305
- }
306
- }
307
-
308
-
309
- // this is how we track which listeners are depending on a remote resource and which
310
- private removeListener(listener: () => Promise<void>): string[] {
311
- const removed: string[] = [];
312
- for (const r of this.listeners.keys()) {
313
- const currentListeners = this.listeners.get(r)!;
314
-
315
- // minor optimization?
316
- if (currentListeners.find(l => l === listener) === undefined) {
317
- continue;
318
- }
319
- removed.push(r);
320
-
321
- const newListeners = currentListeners.filter(l => l !== listener);
322
- this.listeners.set(r, newListeners);
323
- }
324
- return removed;
325
- }
326
-
327
- private increaseRemoteCount(resource: string) {
328
- if (this.remoteListenersCount.has(resource)) {
329
- const c = this.remoteListenersCount.get(resource)!;
330
- this.remoteListenersCount.set(resource, c + 1)
331
- } else {
332
- this.remoteListenersCount.set(resource, 1);
333
- }
334
- }
335
- private decreateRemoteCount(resource: string) {
336
- if (this.remoteListenersCount.has(resource)) {
337
- const c = this.remoteListenersCount.get(resource)!;
338
- if (c === 0) {
339
- throw new Error("Internal covenant error (not your fault unless you are developing covenant): Tried to reduce count for resource already at 0. Please report a bug.")
340
- }
341
- this.remoteListenersCount.set(resource, c - 1)
342
- } else {
343
- throw new Error("Internal covenant error (not your fault unless you are developing covenant): Tried to reduce count for resource with no remote subscriptions. Please report a bug.");
344
- }
345
- }
346
-
347
- private cleanRemoteListeners() {
348
- const unlistenTo: string[] = [];
349
- for (const k of this.remoteListenersCount.keys()) {
350
- const c = this.remoteListenersCount.get(k)!;
351
-
352
- if (c === 0) {
353
- unlistenTo.push(k);
354
- }
355
- }
356
-
357
- this.sidekickConnection.sendMessage({
358
- type: "unlisten",
359
- resources: unlistenTo,
360
- })
361
- }
362
- }
@@ -1,43 +0,0 @@
1
- import type { ClientToServerConnection, ClientToSidekickConnection } from "@covenant-rpc/core/interfaces";
2
-
3
- export function emptyClientToSidekick(): ClientToSidekickConnection {
4
- return {
5
- sendMessage() {
6
-
7
- },
8
- onMessage() {
9
- return () => { };
10
- },
11
- }
12
- }
13
-
14
-
15
- export function emptyClientToServer(): ClientToServerConnection {
16
- return {
17
- async sendConnectionRequest(r) {
18
- return {
19
- channel: r.channel,
20
- params: r.params,
21
- result: {
22
- type: "ERROR",
23
- error: {
24
- params: r.params,
25
- channel: r.channel,
26
- fault: "client",
27
- message: "Empty connection always fails.",
28
- }
29
- }
30
- }
31
- },
32
- async runProcedure() {
33
- return {
34
- status: "ERR",
35
- error: {
36
- code: 501,
37
- message: "Using an empty client to server connection",
38
- },
39
- resources: [],
40
- }
41
- }
42
- }
43
- }
@@ -1,232 +0,0 @@
1
- import type { ClientToServerConnection, ClientToSidekickConnection } from "@covenant-rpc/core/interfaces";
2
- import type { ChannelConnectionRequest, ChannelConnectionResponse } from "@covenant-rpc/core/channel";
3
- import { procedureResponseSchema } from "@covenant-rpc/core/procedure";
4
- import { channelConnectionResponseSchema } from "@covenant-rpc/core/channel";
5
- import { sidekickOutgoingMessageSchema, type SidekickIncomingMessage, type SidekickOutgoingMessage } from "@covenant-rpc/core/sidekick/protocol";
6
- import { isPromise, type MaybePromise } from "@covenant-rpc/core/utils";
7
- import { v } from "@covenant-rpc/core/validation";
8
- import ION from "@covenant-rpc/ion";
9
-
10
- export function httpClientToSidekick(rootUrl: string): ClientToSidekickConnection {
11
- return new HttpClientToSidekick(rootUrl);
12
- }
13
-
14
- class HttpClientToSidekick implements ClientToSidekickConnection {
15
- socketUrl: string
16
- listeners: ((m: SidekickOutgoingMessage) => MaybePromise<void>)[] = [];
17
-
18
- //@ts-expect-error we define this in the reconnectSocket method which is called by the constructor
19
- socket: WebSocket;
20
- ready: boolean = false;
21
- sendQueue: SidekickIncomingMessage[] = []
22
-
23
- constructor(rootUrl: string) {
24
- const url = new URL(rootUrl);
25
- const protocol = url.protocol === "https:" ? "wss" : "ws";
26
- this.socketUrl = `${protocol}://${url.host}/socket`;
27
-
28
- this.reconnectSocket();
29
- }
30
-
31
- private reconnectSocket() {
32
- this.socket = new WebSocket(this.socketUrl);
33
- this.ready = false;
34
-
35
- this.socket.onmessage = async (m) => {
36
- let data: unknown;
37
- try {
38
- data = typeof m.data === 'string' ? ION.parse(m.data) : m.data;
39
- } catch (e) {
40
- await this.emitMessage({
41
- type: "error",
42
- error: {
43
- fault: "sidekick",
44
- message: "Error parsing JSON from sidekick message",
45
- channel: "unknown",
46
- params: {},
47
- }
48
- });
49
- return;
50
- }
51
-
52
- const message = v.parseSafe(data, sidekickOutgoingMessageSchema);
53
-
54
- if (!message) {
55
- await this.emitMessage({
56
- type: "error",
57
- error: {
58
- fault: "sidekick",
59
- message: "Error parsing incoming message from sidekick",
60
- channel: "unknown",
61
- params: {},
62
- }
63
- });
64
- return;
65
- }
66
-
67
- await this.emitMessage(message);
68
- }
69
-
70
- this.socket.onopen = () => {
71
- this.ready = true;
72
-
73
- for (const m of this.sendQueue) {
74
- this.socket.send(ION.stringify(m));
75
- }
76
- }
77
-
78
- this.socket.onclose = () => {
79
- this.ready = false;
80
- }
81
-
82
- this.socket.onerror = async () => {
83
- this.ready = false;
84
-
85
- await this.emitMessage({
86
- type: "error",
87
- error: {
88
- fault: "client",
89
- message: "Unknown websocket error",
90
- channel: "unknown",
91
- params: {},
92
- }
93
- })
94
- }
95
- }
96
-
97
- private async emitMessage(m: SidekickOutgoingMessage) {
98
- const promises: Promise<void>[] = [];
99
- for (const l of this.listeners) {
100
- const p = l(m);
101
- if (isPromise(p)) {
102
- promises.push(p);
103
- }
104
- }
105
-
106
- await Promise.all(promises);
107
- }
108
-
109
- sendMessage(message: SidekickIncomingMessage): void {
110
- if (this.ready) {
111
- const json = ION.stringify(message);
112
- this.socket.send(json);
113
- } else {
114
- this.sendQueue.push(message);
115
- }
116
- }
117
-
118
- onMessage(handler: (m: SidekickOutgoingMessage) => MaybePromise<void>): () => void {
119
- this.listeners.push(handler);
120
-
121
- return () => {
122
- const newListeners = this.listeners.filter(l => l !== handler);
123
- this.listeners = newListeners;
124
- }
125
- }
126
- }
127
-
128
-
129
- export function httpClientToServer(covenantUrl: string, extraHeaders: Record<string, string>): ClientToServerConnection {
130
- const getHeaders = () => {
131
- const h = new Headers();
132
- h.set("Content-Type", "application/json");
133
-
134
- for (const k in extraHeaders) {
135
- h.set(k, extraHeaders[k]!);
136
- }
137
- return h;
138
- }
139
-
140
- const getUrl = (type: string) => {
141
- const url = new URL(covenantUrl);
142
- url.searchParams.set("type", type);
143
- return url.toString();
144
- }
145
-
146
- return {
147
- async sendConnectionRequest(body: ChannelConnectionRequest): Promise<ChannelConnectionResponse> {
148
- try {
149
- const headers = getHeaders();
150
- const url = getUrl("connect");
151
-
152
- const response = await fetch(url, {
153
- method: "POST",
154
- headers,
155
- body: ION.stringify(body),
156
- });
157
-
158
- const responseText = await response.text();
159
- const responseBody = ION.parse(responseText);
160
- const connectionResponse = v.parseSafe(responseBody, channelConnectionResponseSchema);
161
-
162
- if (connectionResponse === null) {
163
- return {
164
- channel: body.channel,
165
- params: body.params,
166
- result: {
167
- type: "ERROR",
168
- error: {
169
- channel: body.channel,
170
- params: body.params,
171
- fault: "server",
172
- message: `Bad response from server: ${JSON.stringify(responseBody)}`,
173
- },
174
- },
175
- };
176
- }
177
-
178
- return connectionResponse;
179
- } catch (e) {
180
- return {
181
- channel: body.channel,
182
- params: body.params,
183
- result: {
184
- type: "ERROR",
185
- error: {
186
- channel: body.channel,
187
- params: body.params,
188
- fault: "server",
189
- message: `Unknown error connecting to channel: ${e}`,
190
- },
191
- },
192
- };
193
- }
194
- },
195
- async runProcedure(body) {
196
- try {
197
- const headers = getHeaders();
198
- const url = getUrl("procedure");
199
-
200
- const response = await fetch(url, {
201
- method: "POST",
202
- headers,
203
- body: ION.stringify(body),
204
- });
205
-
206
- const responseText = await response.text();
207
- const responseBody = ION.parse(responseText);
208
- const procedureResponse = v.parseSafe(responseBody, procedureResponseSchema)
209
-
210
- if (procedureResponse === null) {
211
- return {
212
- status: "ERR",
213
- error: {
214
- code: 500,
215
- message: `Bad response from server: ${responseBody}`,
216
- }
217
- }
218
- }
219
-
220
- return procedureResponse;
221
- } catch (e) {
222
- return {
223
- status: "ERR",
224
- error: {
225
- code: 400,
226
- message: `Unknown error fetching from the server: ${e}`,
227
- }
228
- }
229
- }
230
- }
231
- }
232
- }