@neta-art/cohub 1.0.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.
- package/README.md +132 -0
- package/dist/apis/channels.d.ts +13 -0
- package/dist/apis/channels.js +24 -0
- package/dist/apis/cron-jobs.d.ts +19 -0
- package/dist/apis/cron-jobs.js +32 -0
- package/dist/apis/models.d.ts +8 -0
- package/dist/apis/models.js +17 -0
- package/dist/apis/session-access.d.ts +13 -0
- package/dist/apis/session-access.js +19 -0
- package/dist/apis/spaces.d.ts +177 -0
- package/dist/apis/spaces.js +373 -0
- package/dist/apis/tasks.d.ts +20 -0
- package/dist/apis/tasks.js +25 -0
- package/dist/apis/user.d.ts +20 -0
- package/dist/apis/user.js +58 -0
- package/dist/client.d.ts +22 -0
- package/dist/client.js +38 -0
- package/dist/http.d.ts +24 -0
- package/dist/http.js +34 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/realtime.d.ts +2 -0
- package/dist/realtime.js +5 -0
- package/dist/transport.d.ts +27 -0
- package/dist/transport.js +57 -0
- package/dist/types.d.ts +211 -0
- package/dist/types.js +1 -0
- package/dist/websocket.d.ts +119 -0
- package/dist/websocket.js +398 -0
- package/package.json +56 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { realtimeEnvelopeSchema, } from "@neta-art/cohub-protocol/realtime";
|
|
2
|
+
const createEventMap = () => ({
|
|
3
|
+
open: new Set(),
|
|
4
|
+
close: new Set(),
|
|
5
|
+
error: new Set(),
|
|
6
|
+
event: new Set(),
|
|
7
|
+
ready: new Set(),
|
|
8
|
+
auth: new Set(),
|
|
9
|
+
messageAccepted: new Set(),
|
|
10
|
+
serverError: new Set(),
|
|
11
|
+
pong: new Set(),
|
|
12
|
+
});
|
|
13
|
+
const toWebSocketUrl = (input) => {
|
|
14
|
+
const base = (input?.trim() || "").replace(/\/$/, "");
|
|
15
|
+
if (base) {
|
|
16
|
+
if (base.startsWith("ws://") || base.startsWith("wss://"))
|
|
17
|
+
return `${base}/ws`;
|
|
18
|
+
if (base.startsWith("http://"))
|
|
19
|
+
return `${base.replace(/^http:/, "ws:")}/ws`;
|
|
20
|
+
if (base.startsWith("https://"))
|
|
21
|
+
return `${base.replace(/^https:/, "wss:")}/ws`;
|
|
22
|
+
}
|
|
23
|
+
if (typeof window !== "undefined") {
|
|
24
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
25
|
+
return `${protocol}//${window.location.host}/ws`;
|
|
26
|
+
}
|
|
27
|
+
return "ws://localhost:8788/ws";
|
|
28
|
+
};
|
|
29
|
+
const normalizeOptions = (options = {}) => ({
|
|
30
|
+
url: toWebSocketUrl(options.url),
|
|
31
|
+
autoReconnect: options.autoReconnect !== false,
|
|
32
|
+
reconnectBaseDelayMs: options.reconnectBaseDelayMs ?? 1000,
|
|
33
|
+
reconnectMaxDelayMs: options.reconnectMaxDelayMs ?? 15000,
|
|
34
|
+
pingIntervalMs: options.pingIntervalMs ?? 20000,
|
|
35
|
+
pongTimeoutMs: options.pongTimeoutMs ?? 15000,
|
|
36
|
+
debug: options.debug === true,
|
|
37
|
+
});
|
|
38
|
+
class WebsocketAuthError extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "WebsocketAuthError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class WebsocketClient {
|
|
45
|
+
url;
|
|
46
|
+
autoReconnect;
|
|
47
|
+
reconnectBaseDelayMs;
|
|
48
|
+
reconnectMaxDelayMs;
|
|
49
|
+
pingIntervalMs;
|
|
50
|
+
pongTimeoutMs;
|
|
51
|
+
debug;
|
|
52
|
+
getAccessToken;
|
|
53
|
+
WebSocketImpl;
|
|
54
|
+
ws = null;
|
|
55
|
+
pingTimer = null;
|
|
56
|
+
reconnectTimer = null;
|
|
57
|
+
reconnectAttempt = 0;
|
|
58
|
+
manuallyClosed = false;
|
|
59
|
+
connectPromise = null;
|
|
60
|
+
authWaiter = null;
|
|
61
|
+
awaitingPong = false;
|
|
62
|
+
lastPingRequestId = null;
|
|
63
|
+
pongDeadlineAt = 0;
|
|
64
|
+
state = "idle";
|
|
65
|
+
connectionId = null;
|
|
66
|
+
listeners = createEventMap();
|
|
67
|
+
constructor(options = {}) {
|
|
68
|
+
const normalized = normalizeOptions(options);
|
|
69
|
+
this.url = normalized.url;
|
|
70
|
+
this.autoReconnect = normalized.autoReconnect;
|
|
71
|
+
this.reconnectBaseDelayMs = normalized.reconnectBaseDelayMs;
|
|
72
|
+
this.reconnectMaxDelayMs = normalized.reconnectMaxDelayMs;
|
|
73
|
+
this.pingIntervalMs = normalized.pingIntervalMs;
|
|
74
|
+
this.pongTimeoutMs = normalized.pongTimeoutMs;
|
|
75
|
+
this.debug = normalized.debug;
|
|
76
|
+
this.getAccessToken = options.getAccessToken;
|
|
77
|
+
this.WebSocketImpl = options.WebSocketImpl ?? WebSocket;
|
|
78
|
+
}
|
|
79
|
+
on(type, handler) {
|
|
80
|
+
this.listeners[type].add(handler);
|
|
81
|
+
return () => this.off(type, handler);
|
|
82
|
+
}
|
|
83
|
+
off(type, handler) {
|
|
84
|
+
this.listeners[type].delete(handler);
|
|
85
|
+
}
|
|
86
|
+
emit(type, payload) {
|
|
87
|
+
for (const handler of this.listeners[type]) {
|
|
88
|
+
handler(payload);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
log(...args) {
|
|
92
|
+
if (this.debug)
|
|
93
|
+
console.log("[WebsocketClient]", ...args);
|
|
94
|
+
}
|
|
95
|
+
async connect() {
|
|
96
|
+
if (this.connectPromise)
|
|
97
|
+
return this.connectPromise;
|
|
98
|
+
if (this.state === "open" && this.ws?.readyState === WebSocket.OPEN)
|
|
99
|
+
return;
|
|
100
|
+
this.manuallyClosed = false;
|
|
101
|
+
this.state = "connecting";
|
|
102
|
+
this.connectPromise = new Promise((resolve, reject) => {
|
|
103
|
+
const ws = new this.WebSocketImpl(this.url);
|
|
104
|
+
this.ws = ws;
|
|
105
|
+
let settled = false;
|
|
106
|
+
const rejectOnce = (error) => {
|
|
107
|
+
if (settled)
|
|
108
|
+
return;
|
|
109
|
+
settled = true;
|
|
110
|
+
this.connectPromise = null;
|
|
111
|
+
reject(error);
|
|
112
|
+
};
|
|
113
|
+
const resolveOnce = () => {
|
|
114
|
+
if (settled)
|
|
115
|
+
return;
|
|
116
|
+
settled = true;
|
|
117
|
+
this.connectPromise = null;
|
|
118
|
+
resolve();
|
|
119
|
+
};
|
|
120
|
+
ws.onopen = async () => {
|
|
121
|
+
try {
|
|
122
|
+
this.log("connected", this.url);
|
|
123
|
+
this.startPingLoop();
|
|
124
|
+
await this.authenticate();
|
|
125
|
+
this.state = "open";
|
|
126
|
+
this.reconnectAttempt = 0;
|
|
127
|
+
this.emit("open", { connectionId: this.connectionId });
|
|
128
|
+
resolveOnce();
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
const authError = error instanceof Error ? error : new Error("authentication failed");
|
|
132
|
+
rejectOnce(authError);
|
|
133
|
+
ws.close(4003, authError.message);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
ws.onmessage = (event) => {
|
|
137
|
+
this.handleMessage(event.data);
|
|
138
|
+
};
|
|
139
|
+
ws.onerror = (error) => {
|
|
140
|
+
this.emit("error", { error });
|
|
141
|
+
};
|
|
142
|
+
ws.onclose = (event) => {
|
|
143
|
+
this.stopPingLoop();
|
|
144
|
+
const wasConnecting = this.state === "connecting";
|
|
145
|
+
this.state = "closed";
|
|
146
|
+
this.ws = null;
|
|
147
|
+
this.rejectAuthWaiter(new Error(`WebSocket closed: ${event.code} ${event.reason || ""}`.trim()));
|
|
148
|
+
const willReconnect = !this.manuallyClosed && this.autoReconnect;
|
|
149
|
+
this.emit("close", {
|
|
150
|
+
code: event.code,
|
|
151
|
+
reason: event.reason,
|
|
152
|
+
willReconnect,
|
|
153
|
+
});
|
|
154
|
+
if (wasConnecting) {
|
|
155
|
+
rejectOnce(new Error(`WebSocket closed: ${event.code} ${event.reason || ""}`.trim()));
|
|
156
|
+
if (event.code === 4001 && willReconnect) {
|
|
157
|
+
void this.scheduleReconnect();
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (willReconnect) {
|
|
162
|
+
void this.scheduleReconnect();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
return this.connectPromise;
|
|
167
|
+
}
|
|
168
|
+
async disconnect(code = 1000, reason = "manual") {
|
|
169
|
+
this.manuallyClosed = true;
|
|
170
|
+
this.clearReconnectTimer();
|
|
171
|
+
this.stopPingLoop();
|
|
172
|
+
this.state = "closed";
|
|
173
|
+
this.rejectAuthWaiter(new Error("disconnected"));
|
|
174
|
+
this.ws?.close(code, reason);
|
|
175
|
+
this.ws = null;
|
|
176
|
+
this.connectPromise = null;
|
|
177
|
+
}
|
|
178
|
+
async sendMessage(input) {
|
|
179
|
+
await this.ensureOpen();
|
|
180
|
+
this.send({
|
|
181
|
+
type: "session.message.create",
|
|
182
|
+
requestId: input.requestId,
|
|
183
|
+
payload: {
|
|
184
|
+
spaceId: input.spaceId,
|
|
185
|
+
sessionId: input.sessionId,
|
|
186
|
+
content: input.content,
|
|
187
|
+
clientMessageId: input.clientMessageId,
|
|
188
|
+
model: input.model,
|
|
189
|
+
provider: input.provider,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
ack(eventId, requestId) {
|
|
194
|
+
this.send({
|
|
195
|
+
type: "ack",
|
|
196
|
+
requestId,
|
|
197
|
+
payload: eventId ? { eventId } : undefined,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
ping(requestId) {
|
|
201
|
+
const effectiveRequestId = requestId ?? `ping-${Date.now()}`;
|
|
202
|
+
this.awaitingPong = true;
|
|
203
|
+
this.lastPingRequestId = effectiveRequestId;
|
|
204
|
+
this.pongDeadlineAt = Date.now() + this.pongTimeoutMs;
|
|
205
|
+
this.send({ type: "ping", requestId: effectiveRequestId, payload: {} });
|
|
206
|
+
}
|
|
207
|
+
async ensureOpen() {
|
|
208
|
+
if (this.state === "open" && this.ws?.readyState === WebSocket.OPEN)
|
|
209
|
+
return;
|
|
210
|
+
await this.connect();
|
|
211
|
+
}
|
|
212
|
+
send(event) {
|
|
213
|
+
const ws = this.ws;
|
|
214
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
215
|
+
throw new Error("websocket is not open");
|
|
216
|
+
}
|
|
217
|
+
ws.send(JSON.stringify(event));
|
|
218
|
+
}
|
|
219
|
+
async authenticate() {
|
|
220
|
+
const token = this.getAccessToken ? await this.getAccessToken() : null;
|
|
221
|
+
if (!token)
|
|
222
|
+
throw new WebsocketAuthError("missing access token");
|
|
223
|
+
const waiter = this.createAuthWaiter();
|
|
224
|
+
this.send({ type: "auth", payload: { token } });
|
|
225
|
+
await waiter.promise;
|
|
226
|
+
}
|
|
227
|
+
createAuthWaiter() {
|
|
228
|
+
this.rejectAuthWaiter(new Error("superseded auth waiter"));
|
|
229
|
+
let resolve;
|
|
230
|
+
let reject;
|
|
231
|
+
const promise = new Promise((res, rej) => {
|
|
232
|
+
resolve = res;
|
|
233
|
+
reject = rej;
|
|
234
|
+
});
|
|
235
|
+
this.authWaiter = { promise, resolve, reject };
|
|
236
|
+
return this.authWaiter;
|
|
237
|
+
}
|
|
238
|
+
resolveAuthWaiter() {
|
|
239
|
+
if (!this.authWaiter)
|
|
240
|
+
return;
|
|
241
|
+
this.authWaiter.resolve();
|
|
242
|
+
this.authWaiter = null;
|
|
243
|
+
}
|
|
244
|
+
rejectAuthWaiter(error) {
|
|
245
|
+
if (!this.authWaiter)
|
|
246
|
+
return;
|
|
247
|
+
this.authWaiter.reject(error);
|
|
248
|
+
this.authWaiter = null;
|
|
249
|
+
}
|
|
250
|
+
handleMessage(raw) {
|
|
251
|
+
let parsed;
|
|
252
|
+
try {
|
|
253
|
+
parsed = typeof raw === "string" ? JSON.parse(raw) : JSON.parse(String(raw));
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
this.emit("error", { error: new Error("invalid websocket payload") });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const result = realtimeEnvelopeSchema.safeParse(parsed);
|
|
260
|
+
if (!result.success) {
|
|
261
|
+
this.emit("error", { error: new Error("invalid realtime envelope") });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const envelope = result.data;
|
|
265
|
+
switch (envelope.type) {
|
|
266
|
+
case "system.ready": {
|
|
267
|
+
const connectionId = typeof envelope.payload.connectionId === "string"
|
|
268
|
+
? envelope.payload.connectionId
|
|
269
|
+
: null;
|
|
270
|
+
if (connectionId) {
|
|
271
|
+
this.connectionId = connectionId;
|
|
272
|
+
this.emit("ready", { connectionId });
|
|
273
|
+
}
|
|
274
|
+
this.emit("event", envelope);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
case "system.auth.ok": {
|
|
278
|
+
const connectionId = typeof envelope.payload.connectionId === "string"
|
|
279
|
+
? envelope.payload.connectionId
|
|
280
|
+
: this.connectionId;
|
|
281
|
+
const user = envelope.payload.user && typeof envelope.payload.user === "object"
|
|
282
|
+
? envelope.payload.user
|
|
283
|
+
: {};
|
|
284
|
+
if (connectionId) {
|
|
285
|
+
this.connectionId = connectionId;
|
|
286
|
+
this.emit("auth", { connectionId, user });
|
|
287
|
+
}
|
|
288
|
+
this.resolveAuthWaiter();
|
|
289
|
+
this.emit("event", envelope);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
case "system.request.error": {
|
|
293
|
+
const message = typeof envelope.payload.message === "string"
|
|
294
|
+
? envelope.payload.message
|
|
295
|
+
: "request failed";
|
|
296
|
+
const code = typeof envelope.payload.code === "string"
|
|
297
|
+
? envelope.payload.code
|
|
298
|
+
: undefined;
|
|
299
|
+
const error = new WebsocketAuthError(message);
|
|
300
|
+
this.rejectAuthWaiter(error);
|
|
301
|
+
this.emit("serverError", {
|
|
302
|
+
code,
|
|
303
|
+
message,
|
|
304
|
+
requestId: envelope.requestId ?? null,
|
|
305
|
+
sessionId: envelope.sessionId ?? null,
|
|
306
|
+
spaceId: envelope.spaceId ?? null,
|
|
307
|
+
});
|
|
308
|
+
this.emit("event", envelope);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
case "session.request.accepted": {
|
|
312
|
+
const payload = envelope.payload;
|
|
313
|
+
this.emit("messageAccepted", {
|
|
314
|
+
requestId: envelope.requestId ?? null,
|
|
315
|
+
sessionId: envelope.sessionId ?? null,
|
|
316
|
+
spaceId: envelope.spaceId ?? null,
|
|
317
|
+
clientMessageId: typeof payload.clientMessageId === "string" ? payload.clientMessageId : null,
|
|
318
|
+
});
|
|
319
|
+
this.emit("event", envelope);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
case "session.request.error": {
|
|
323
|
+
const payload = envelope.payload;
|
|
324
|
+
this.emit("serverError", {
|
|
325
|
+
code: typeof payload.code === "string" ? payload.code : undefined,
|
|
326
|
+
message: typeof payload.message === "string" ? payload.message : undefined,
|
|
327
|
+
requestId: envelope.requestId ?? null,
|
|
328
|
+
sessionId: envelope.sessionId ?? null,
|
|
329
|
+
spaceId: envelope.spaceId ?? null,
|
|
330
|
+
clientMessageId: typeof payload.clientMessageId === "string" ? payload.clientMessageId : null,
|
|
331
|
+
});
|
|
332
|
+
this.emit("event", envelope);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
case "system.pong": {
|
|
336
|
+
const requestId = envelope.requestId ?? null;
|
|
337
|
+
if (!requestId || requestId === this.lastPingRequestId) {
|
|
338
|
+
this.awaitingPong = false;
|
|
339
|
+
this.lastPingRequestId = null;
|
|
340
|
+
this.pongDeadlineAt = 0;
|
|
341
|
+
}
|
|
342
|
+
this.emit("pong", { requestId });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
case "system.ack.ok": {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
default: {
|
|
349
|
+
this.emit("event", envelope);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
startPingLoop() {
|
|
355
|
+
this.stopPingLoop();
|
|
356
|
+
this.pingTimer = setInterval(() => {
|
|
357
|
+
if (this.ws?.readyState !== WebSocket.OPEN)
|
|
358
|
+
return;
|
|
359
|
+
if (this.awaitingPong &&
|
|
360
|
+
this.pongDeadlineAt > 0 &&
|
|
361
|
+
Date.now() > this.pongDeadlineAt) {
|
|
362
|
+
this.emit("error", { error: new Error("websocket pong timeout") });
|
|
363
|
+
this.ws.close(4002, "pong timeout");
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
this.ping();
|
|
367
|
+
}, this.pingIntervalMs);
|
|
368
|
+
}
|
|
369
|
+
stopPingLoop() {
|
|
370
|
+
if (this.pingTimer) {
|
|
371
|
+
clearInterval(this.pingTimer);
|
|
372
|
+
this.pingTimer = null;
|
|
373
|
+
}
|
|
374
|
+
this.awaitingPong = false;
|
|
375
|
+
this.lastPingRequestId = null;
|
|
376
|
+
this.pongDeadlineAt = 0;
|
|
377
|
+
}
|
|
378
|
+
clearReconnectTimer() {
|
|
379
|
+
if (!this.reconnectTimer)
|
|
380
|
+
return;
|
|
381
|
+
clearTimeout(this.reconnectTimer);
|
|
382
|
+
this.reconnectTimer = null;
|
|
383
|
+
}
|
|
384
|
+
async scheduleReconnect() {
|
|
385
|
+
this.clearReconnectTimer();
|
|
386
|
+
const delay = Math.min(this.reconnectBaseDelayMs * 2 ** this.reconnectAttempt, this.reconnectMaxDelayMs);
|
|
387
|
+
this.reconnectAttempt += 1;
|
|
388
|
+
await new Promise((resolve) => {
|
|
389
|
+
this.reconnectTimer = setTimeout(() => resolve(), delay);
|
|
390
|
+
});
|
|
391
|
+
if (this.manuallyClosed)
|
|
392
|
+
return;
|
|
393
|
+
await this.connect().catch((error) => {
|
|
394
|
+
this.emit("error", { error });
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
export const createWebsocketClient = (options) => new WebsocketClient(options);
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neta-art/cohub",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Cohub SDK for spaces, sessions, checkpoints, and realtime agent collaboration.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/neta-art/cohub.git",
|
|
12
|
+
"directory": "packages/sdk"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/neta-art/cohub",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"cohub",
|
|
17
|
+
"sdk",
|
|
18
|
+
"ai",
|
|
19
|
+
"agent",
|
|
20
|
+
"spaces",
|
|
21
|
+
"realtime",
|
|
22
|
+
"websocket"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./http": {
|
|
33
|
+
"types": "./dist/http.d.ts",
|
|
34
|
+
"import": "./dist/http.js"
|
|
35
|
+
},
|
|
36
|
+
"./websocket": {
|
|
37
|
+
"types": "./dist/websocket.d.ts",
|
|
38
|
+
"import": "./dist/websocket.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@neta-art/cohub-protocol": "^1.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"typescript": "^6.0.3"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc -p tsconfig.build.json",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
55
|
+
}
|
|
56
|
+
}
|