doover-js 0.2.0-alpha.0 → 0.2.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/dist/apis/connections-api.d.ts +6 -0
- package/dist/apis/connections-api.js +8 -0
- package/dist/client/doover-client.d.ts +13 -0
- package/dist/client/doover-client.js +30 -2
- package/dist/client/singleton.d.ts +26 -0
- package/dist/client/singleton.js +59 -0
- package/dist/client/stats.d.ts +55 -0
- package/dist/client/stats.js +97 -0
- package/dist/gateway/gateway-client.d.ts +30 -6
- package/dist/gateway/gateway-client.js +125 -46
- package/dist/gateway/types.d.ts +0 -1
- package/dist/http/rest-client.d.ts +11 -0
- package/dist/http/rest-client.js +17 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -1
- package/dist/react/context.d.ts +19 -0
- package/dist/react/context.js +28 -0
- package/dist/react/index.d.ts +24 -0
- package/dist/react/index.js +40 -0
- package/dist/react/sharedQueryClient.d.ts +21 -0
- package/dist/react/sharedQueryClient.js +39 -0
- package/dist/react/useAgentChannel.d.ts +7 -0
- package/dist/react/useAgentChannel.js +12 -0
- package/dist/react/useAgentConnections.d.ts +8 -0
- package/dist/react/useAgentConnections.js +24 -0
- package/dist/react/useChannelAggregate.d.ts +13 -0
- package/dist/react/useChannelAggregate.js +38 -0
- package/dist/react/useChannelMessages.d.ts +23 -0
- package/dist/react/useChannelMessages.js +56 -0
- package/dist/react/useChannelSubscription.d.ts +16 -0
- package/dist/react/useChannelSubscription.js +43 -0
- package/dist/react/useConnectionState.d.ts +21 -0
- package/dist/react/useConnectionState.js +43 -0
- package/dist/react/useMultiAgentAggregates.d.ts +22 -0
- package/dist/react/useMultiAgentAggregates.js +82 -0
- package/dist/react/useMultiAgentChannelMessages.d.ts +18 -0
- package/dist/react/useMultiAgentChannelMessages.js +78 -0
- package/dist/react/useSendMessage.d.ts +9 -0
- package/dist/react/useSendMessage.js +16 -0
- package/dist/react/useSendRpc.d.ts +61 -0
- package/dist/react/useSendRpc.js +158 -0
- package/dist/react/useTurnCredentials.d.ts +12 -0
- package/dist/react/useTurnCredentials.js +25 -0
- package/dist/react/useUpdateAggregate.d.ts +14 -0
- package/dist/react/useUpdateAggregate.js +25 -0
- package/dist/react/useUpdateMessage.d.ts +17 -0
- package/dist/react/useUpdateMessage.js +24 -0
- package/dist/test/apis.test.js +7 -1
- package/dist/test/doover-client.test.js +13 -0
- package/dist/test/doover-data-provider.test.js +155 -0
- package/dist/test/gateway-client.test.js +122 -8
- package/dist/test/react.test.d.ts +1 -0
- package/dist/test/react.test.js +353 -0
- package/dist/test/singleton.test.d.ts +1 -0
- package/dist/test/singleton.test.js +62 -0
- package/dist/types/common.d.ts +40 -0
- package/dist/viewer/doover-data-provider.d.ts +32 -4
- package/dist/viewer/doover-data-provider.js +92 -5
- package/package.json +28 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const chai_1 = require("chai");
|
|
7
|
+
const mocha_1 = require("mocha");
|
|
8
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
9
|
+
const doover_client_1 = require("../client/doover-client");
|
|
10
|
+
const singleton_1 = require("../client/singleton");
|
|
11
|
+
const helpers_1 = require("./helpers");
|
|
12
|
+
(0, mocha_1.describe)("getDooverClient", () => {
|
|
13
|
+
(0, mocha_1.beforeEach)(() => {
|
|
14
|
+
(0, helpers_1.installSessionStorageMock)();
|
|
15
|
+
helpers_1.MockWebSocket.reset();
|
|
16
|
+
Object.defineProperty(globalThis, "WebSocket", {
|
|
17
|
+
value: helpers_1.MockWebSocket,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
});
|
|
21
|
+
(0, singleton_1.resetDooverClient)();
|
|
22
|
+
});
|
|
23
|
+
(0, mocha_1.afterEach)(() => {
|
|
24
|
+
sinon_1.default.restore();
|
|
25
|
+
(0, singleton_1.resetDooverClient)();
|
|
26
|
+
});
|
|
27
|
+
const config = {
|
|
28
|
+
dataRestUrl: "https://api.example.com",
|
|
29
|
+
controlApiUrl: "https://control.example.com",
|
|
30
|
+
dataWssUrl: "wss://ws.example.com",
|
|
31
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
32
|
+
disableBrowserLifecycleHooks: true,
|
|
33
|
+
};
|
|
34
|
+
(0, mocha_1.it)("creates a client on first call and returns the same instance after", () => {
|
|
35
|
+
const a = (0, singleton_1.getDooverClient)(config);
|
|
36
|
+
const b = (0, singleton_1.getDooverClient)(config);
|
|
37
|
+
(0, chai_1.expect)(a).to.be.instanceOf(doover_client_1.DooverClient);
|
|
38
|
+
(0, chai_1.expect)(a).to.equal(b);
|
|
39
|
+
});
|
|
40
|
+
(0, mocha_1.it)("peekDooverClient returns null until the first call, then the instance", () => {
|
|
41
|
+
(0, chai_1.expect)((0, singleton_1.peekDooverClient)()).to.equal(null);
|
|
42
|
+
const a = (0, singleton_1.getDooverClient)(config);
|
|
43
|
+
(0, chai_1.expect)((0, singleton_1.peekDooverClient)()).to.equal(a);
|
|
44
|
+
});
|
|
45
|
+
(0, mocha_1.it)("resetDooverClient clears the singleton so the next call creates a new one", () => {
|
|
46
|
+
const a = (0, singleton_1.getDooverClient)(config);
|
|
47
|
+
(0, singleton_1.resetDooverClient)();
|
|
48
|
+
const b = (0, singleton_1.getDooverClient)(config);
|
|
49
|
+
(0, chai_1.expect)(a).to.not.equal(b);
|
|
50
|
+
});
|
|
51
|
+
(0, mocha_1.it)("warns and reuses the existing client when a later caller differs in config", () => {
|
|
52
|
+
const warn = sinon_1.default.stub(console, "warn");
|
|
53
|
+
const a = (0, singleton_1.getDooverClient)(config);
|
|
54
|
+
const b = (0, singleton_1.getDooverClient)({
|
|
55
|
+
...config,
|
|
56
|
+
dataRestUrl: "https://other.example.com",
|
|
57
|
+
});
|
|
58
|
+
(0, chai_1.expect)(b).to.equal(a);
|
|
59
|
+
(0, chai_1.expect)(warn.calledOnce).to.equal(true);
|
|
60
|
+
(0, chai_1.expect)(warn.getCall(0).args[0]).to.match(/differs from the already-initialised singleton/);
|
|
61
|
+
});
|
|
62
|
+
});
|
package/dist/types/common.d.ts
CHANGED
|
@@ -128,3 +128,43 @@ export interface DataSeries {
|
|
|
128
128
|
count: number;
|
|
129
129
|
results: DataSeriesResult[];
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Lifecycle status for an RPC. `pending` carries arbitrary intermediate
|
|
133
|
+
* payloads (progress updates) emitted by the server while the request is
|
|
134
|
+
* in flight.
|
|
135
|
+
*/
|
|
136
|
+
export type RpcStatus<TPending = undefined> = {
|
|
137
|
+
code: "awaiting_confirmation";
|
|
138
|
+
} | {
|
|
139
|
+
code: "sent";
|
|
140
|
+
} | {
|
|
141
|
+
code: "acknowledged";
|
|
142
|
+
message: {
|
|
143
|
+
timestamp: number;
|
|
144
|
+
};
|
|
145
|
+
} | {
|
|
146
|
+
code: "error";
|
|
147
|
+
message: string | object;
|
|
148
|
+
} | {
|
|
149
|
+
code: "deferred";
|
|
150
|
+
message: {
|
|
151
|
+
until: number;
|
|
152
|
+
at: number;
|
|
153
|
+
};
|
|
154
|
+
} | {
|
|
155
|
+
code: "pending";
|
|
156
|
+
message: TPending;
|
|
157
|
+
} | {
|
|
158
|
+
code: "success";
|
|
159
|
+
};
|
|
160
|
+
/** Shape a consumer posts to initiate an RPC. */
|
|
161
|
+
export interface RpcRequest<TRequest = object> {
|
|
162
|
+
app_key?: string;
|
|
163
|
+
method: string;
|
|
164
|
+
request: TRequest;
|
|
165
|
+
}
|
|
166
|
+
/** Shape of the message the server maintains as the RPC progresses. */
|
|
167
|
+
export interface RpcMessageData<TRequest = object, TResponse = object, TPending = undefined> extends RpcRequest<TRequest> {
|
|
168
|
+
status: RpcStatus<TPending>;
|
|
169
|
+
response: TResponse;
|
|
170
|
+
}
|
|
@@ -2,15 +2,16 @@ import { AgentsApi } from "../apis/agents-api";
|
|
|
2
2
|
import { AggregatesApi } from "../apis/aggregates-api";
|
|
3
3
|
import { ChannelsApi } from "../apis/channels-api";
|
|
4
4
|
import { ConnectionsApi } from "../apis/connections-api";
|
|
5
|
-
import { MessagesApi } from "../apis/messages-api";
|
|
5
|
+
import { MessagesApi, type ListMessagesParams } from "../apis/messages-api";
|
|
6
6
|
import type { DooverAuth } from "../auth/doover-auth";
|
|
7
7
|
import { GatewayClient } from "../gateway/gateway-client";
|
|
8
8
|
import { RestClient, type DooverClientConfig } from "../http/rest-client";
|
|
9
|
-
import type { Aggregate, Channel, ConnectionDetails, MessageStructure } from "../types/common";
|
|
9
|
+
import type { Aggregate, Channel, ConnectionDetails, MessageStructure, RpcRequest, RpcStatus } from "../types/common";
|
|
10
10
|
import type { Agent, AgentsResponse, ChannelIdentifier, ChannelsDataProvider, SubscriptionDetails, User } from "../types/viewer";
|
|
11
11
|
interface SubscriptionEntry {
|
|
12
12
|
messageCallback: (identifier: ChannelIdentifier, message: MessageStructure) => void;
|
|
13
13
|
aggregateCallback: (identifier: ChannelIdentifier, aggregate: Aggregate) => void;
|
|
14
|
+
messageUpdateCallback?: (identifier: ChannelIdentifier, message: MessageStructure) => void;
|
|
14
15
|
}
|
|
15
16
|
export declare class DooverDataProvider implements ChannelsDataProvider<ChannelIdentifier> {
|
|
16
17
|
private readonly config;
|
|
@@ -34,12 +35,39 @@ export declare class DooverDataProvider implements ChannelsDataProvider<ChannelI
|
|
|
34
35
|
archiveChannel(identifier: ChannelIdentifier): Promise<unknown>;
|
|
35
36
|
unarchiveChannel(identifier: ChannelIdentifier): Promise<unknown>;
|
|
36
37
|
getChannel(identifier: ChannelIdentifier): Promise<Channel | undefined>;
|
|
37
|
-
subscribeToChannel(identifier: ChannelIdentifier, callback: SubscriptionEntry["messageCallback"], aggregateCallback: SubscriptionEntry["aggregateCallback"]): Promise<void>;
|
|
38
|
+
subscribeToChannel(identifier: ChannelIdentifier, callback: SubscriptionEntry["messageCallback"], aggregateCallback: SubscriptionEntry["aggregateCallback"], messageUpdateCallback?: SubscriptionEntry["messageUpdateCallback"]): Promise<void>;
|
|
38
39
|
unsubscribeFromChannel(identifier: ChannelIdentifier, callback: SubscriptionEntry["messageCallback"]): Promise<void>;
|
|
39
40
|
getAggregate(identifier: ChannelIdentifier): Promise<Aggregate | undefined>;
|
|
40
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Fetch a page of messages for a channel.
|
|
43
|
+
*
|
|
44
|
+
* Accepts either a cursor string (legacy — shorthand for `{ before }`)
|
|
45
|
+
* or a `ListMessagesParams` options object.
|
|
46
|
+
*
|
|
47
|
+
* Default limit is 10 for backwards compatibility; pass `limit` explicitly
|
|
48
|
+
* for larger pages.
|
|
49
|
+
*/
|
|
50
|
+
getMessages(identifier: ChannelIdentifier, optionsOrBefore?: string | ListMessagesParams): Promise<MessageStructure[] | undefined>;
|
|
41
51
|
deleteMessage(identifier: ChannelIdentifier, messageId: string): Promise<unknown>;
|
|
42
52
|
sendMessage(identifier: ChannelIdentifier, message: object): Promise<MessageStructure>;
|
|
53
|
+
/**
|
|
54
|
+
* Send a message-based RPC to a channel and await a settled response.
|
|
55
|
+
*
|
|
56
|
+
* Wire protocol: POST a message with `{ type: "rpc", ...rpcRequest }`; the
|
|
57
|
+
* server echoes progress via `MessageUpdate` events keyed by the message id,
|
|
58
|
+
* carrying a `status` envelope. Resolves on `success` with the `response`
|
|
59
|
+
* payload, rejects on `error` with the status message.
|
|
60
|
+
*
|
|
61
|
+
* For low-latency ephemeral commands (camera PTZ etc.), use
|
|
62
|
+
* `gateway.sendOneShotMessage` directly instead — that path is not persisted
|
|
63
|
+
* to the database.
|
|
64
|
+
*
|
|
65
|
+
* `onStatus` fires on every intermediate status (`sent`, `acknowledged`,
|
|
66
|
+
* `deferred`, `pending`) and once more on terminal states.
|
|
67
|
+
*/
|
|
68
|
+
sendRPC<TRequest = object, TResponse = object, TPending = undefined>(identifier: ChannelIdentifier, rpcRequest: RpcRequest<TRequest>, options?: {
|
|
69
|
+
onStatus?: (status: RpcStatus<TPending>) => void;
|
|
70
|
+
}): Promise<TResponse>;
|
|
43
71
|
updateAggregate(identifier: ChannelIdentifier, message: object): Promise<Aggregate>;
|
|
44
72
|
putAggregate(identifier: ChannelIdentifier, message: object): Promise<Aggregate>;
|
|
45
73
|
getChannelSubscriptions(identifier: ChannelIdentifier): Promise<SubscriptionDetails[]>;
|
|
@@ -11,6 +11,13 @@ const errors_1 = require("../http/errors");
|
|
|
11
11
|
const rest_client_1 = require("../http/rest-client");
|
|
12
12
|
const snowflake_1 = require("../utils/snowflake");
|
|
13
13
|
const path_parsing_1 = require("./path-parsing");
|
|
14
|
+
function isRpcMessageData(data) {
|
|
15
|
+
return (typeof data === "object" &&
|
|
16
|
+
data !== null &&
|
|
17
|
+
"status" in data &&
|
|
18
|
+
"method" in data &&
|
|
19
|
+
"request" in data);
|
|
20
|
+
}
|
|
14
21
|
class DooverDataProvider {
|
|
15
22
|
constructor(config, auth) {
|
|
16
23
|
this.config = config;
|
|
@@ -27,6 +34,11 @@ class DooverDataProvider {
|
|
|
27
34
|
const identifier = this.toIdentifier(message.channel.agent_id, message.channel.name);
|
|
28
35
|
this.subscriptions.get(key)?.forEach((entry) => entry.messageCallback(identifier, message));
|
|
29
36
|
});
|
|
37
|
+
this.gateway.on("messageUpdate", (message) => {
|
|
38
|
+
const key = this.channelKey(message.channel.agent_id, message.channel.name);
|
|
39
|
+
const identifier = this.toIdentifier(message.channel.agent_id, message.channel.name);
|
|
40
|
+
this.subscriptions.get(key)?.forEach((entry) => entry.messageUpdateCallback?.(identifier, message));
|
|
41
|
+
});
|
|
30
42
|
this.gateway.on("channelSync", (event) => {
|
|
31
43
|
const key = this.channelKey(event.channel.agent_id, event.channel.name);
|
|
32
44
|
const identifier = this.toIdentifier(event.channel.agent_id, event.channel.name);
|
|
@@ -78,11 +90,15 @@ class DooverDataProvider {
|
|
|
78
90
|
}
|
|
79
91
|
return this.channelsApi.getChannel(identifier.agentId, identifier.channelName);
|
|
80
92
|
}
|
|
81
|
-
async subscribeToChannel(identifier, callback, aggregateCallback) {
|
|
93
|
+
async subscribeToChannel(identifier, callback, aggregateCallback, messageUpdateCallback) {
|
|
82
94
|
const validated = this.requireChannel(identifier);
|
|
83
95
|
const key = this.channelKey(validated.agentId, validated.channelName);
|
|
84
96
|
const entries = this.subscriptions.get(key) ?? [];
|
|
85
|
-
entries.push({
|
|
97
|
+
entries.push({
|
|
98
|
+
messageCallback: callback,
|
|
99
|
+
aggregateCallback,
|
|
100
|
+
messageUpdateCallback,
|
|
101
|
+
});
|
|
86
102
|
this.subscriptions.set(key, entries);
|
|
87
103
|
await this.gateway.connect();
|
|
88
104
|
this.gateway.subscribe({
|
|
@@ -121,13 +137,27 @@ class DooverDataProvider {
|
|
|
121
137
|
}
|
|
122
138
|
return this.aggregatesApi.getAggregate(identifier.agentId, identifier.channelName);
|
|
123
139
|
}
|
|
124
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Fetch a page of messages for a channel.
|
|
142
|
+
*
|
|
143
|
+
* Accepts either a cursor string (legacy — shorthand for `{ before }`)
|
|
144
|
+
* or a `ListMessagesParams` options object.
|
|
145
|
+
*
|
|
146
|
+
* Default limit is 10 for backwards compatibility; pass `limit` explicitly
|
|
147
|
+
* for larger pages.
|
|
148
|
+
*/
|
|
149
|
+
async getMessages(identifier, optionsOrBefore) {
|
|
125
150
|
if (!identifier.agentId || !identifier.channelName) {
|
|
126
151
|
return undefined;
|
|
127
152
|
}
|
|
153
|
+
const options = typeof optionsOrBefore === "string"
|
|
154
|
+
? { before: optionsOrBefore }
|
|
155
|
+
: (optionsOrBefore ?? {});
|
|
128
156
|
const messages = await this.messagesApi.listMessages(identifier.agentId, identifier.channelName, {
|
|
129
|
-
before:
|
|
130
|
-
limit: 10,
|
|
157
|
+
before: options.before ?? (0, snowflake_1.generateSnowflakeIdAtTime)(new Date()),
|
|
158
|
+
limit: options.limit ?? 10,
|
|
159
|
+
...(options.after !== undefined ? { after: options.after } : {}),
|
|
160
|
+
...(options.field_name !== undefined ? { field_name: options.field_name } : {}),
|
|
131
161
|
});
|
|
132
162
|
return messages.reverse();
|
|
133
163
|
}
|
|
@@ -141,6 +171,63 @@ class DooverDataProvider {
|
|
|
141
171
|
data: message,
|
|
142
172
|
});
|
|
143
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Send a message-based RPC to a channel and await a settled response.
|
|
176
|
+
*
|
|
177
|
+
* Wire protocol: POST a message with `{ type: "rpc", ...rpcRequest }`; the
|
|
178
|
+
* server echoes progress via `MessageUpdate` events keyed by the message id,
|
|
179
|
+
* carrying a `status` envelope. Resolves on `success` with the `response`
|
|
180
|
+
* payload, rejects on `error` with the status message.
|
|
181
|
+
*
|
|
182
|
+
* For low-latency ephemeral commands (camera PTZ etc.), use
|
|
183
|
+
* `gateway.sendOneShotMessage` directly instead — that path is not persisted
|
|
184
|
+
* to the database.
|
|
185
|
+
*
|
|
186
|
+
* `onStatus` fires on every intermediate status (`sent`, `acknowledged`,
|
|
187
|
+
* `deferred`, `pending`) and once more on terminal states.
|
|
188
|
+
*/
|
|
189
|
+
sendRPC(identifier, rpcRequest, options) {
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
let rpcMessageId;
|
|
192
|
+
let settled = false;
|
|
193
|
+
const onUpdate = (_identifier, message) => {
|
|
194
|
+
if (settled)
|
|
195
|
+
return;
|
|
196
|
+
if (rpcMessageId === undefined || message.id !== rpcMessageId)
|
|
197
|
+
return;
|
|
198
|
+
if (!isRpcMessageData(message.data))
|
|
199
|
+
return;
|
|
200
|
+
const rpcMessage = message.data;
|
|
201
|
+
options?.onStatus?.(rpcMessage.status);
|
|
202
|
+
if (rpcMessage.status.code === "success") {
|
|
203
|
+
settled = true;
|
|
204
|
+
void this.unsubscribeFromChannel(identifier, noopMessageCallback);
|
|
205
|
+
resolve(rpcMessage.response);
|
|
206
|
+
}
|
|
207
|
+
else if (rpcMessage.status.code === "error") {
|
|
208
|
+
settled = true;
|
|
209
|
+
void this.unsubscribeFromChannel(identifier, noopMessageCallback);
|
|
210
|
+
reject(rpcMessage.status.message);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
// noopMessageCallback is used as the identity handle for this
|
|
214
|
+
// subscription — subscribe/unsubscribe pair matches by reference.
|
|
215
|
+
const noopMessageCallback = () => { };
|
|
216
|
+
const noopAggregateCallback = () => { };
|
|
217
|
+
this.subscribeToChannel(identifier, noopMessageCallback, noopAggregateCallback, onUpdate)
|
|
218
|
+
.then(() => this.sendMessage(identifier, { type: "rpc", ...rpcRequest }))
|
|
219
|
+
.then((message) => {
|
|
220
|
+
rpcMessageId = message.id;
|
|
221
|
+
})
|
|
222
|
+
.catch((error) => {
|
|
223
|
+
if (settled)
|
|
224
|
+
return;
|
|
225
|
+
settled = true;
|
|
226
|
+
void this.unsubscribeFromChannel(identifier, noopMessageCallback);
|
|
227
|
+
reject(error);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
144
231
|
updateAggregate(identifier, message) {
|
|
145
232
|
const validated = this.requireChannel(identifier);
|
|
146
233
|
return this.aggregatesApi.patchAggregate(validated.agentId, validated.channelName, message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doover-js",
|
|
3
|
-
"version": "0.2.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "TypeScript client for Doover.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"./node": {
|
|
13
13
|
"types": "./dist/node/index.d.ts",
|
|
14
14
|
"default": "./dist/node/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./react": {
|
|
17
|
+
"types": "./dist/react/index.d.ts",
|
|
18
|
+
"default": "./dist/react/index.js"
|
|
15
19
|
}
|
|
16
20
|
},
|
|
17
21
|
"repository": {
|
|
@@ -29,21 +33,43 @@
|
|
|
29
33
|
"build": "tsc",
|
|
30
34
|
"clean": "rm -rf dist",
|
|
31
35
|
"prepublishOnly": "npm run build",
|
|
32
|
-
"test": "mocha --require tsx/cjs \"src/test/**/*.test.ts\""
|
|
36
|
+
"test:node": "mocha --exit --require tsx/cjs \"src/test/**/*.test.ts\"",
|
|
37
|
+
"test:react": "mocha --exit --require global-jsdom/register --require tsx/cjs \"src/test/**/*.test.tsx\"",
|
|
38
|
+
"test": "npm run test:node && npm run test:react"
|
|
33
39
|
},
|
|
34
40
|
"keywords": [
|
|
35
41
|
"doover"
|
|
36
42
|
],
|
|
37
43
|
"license": "Apache-2.0",
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@tanstack/react-query": "^5",
|
|
46
|
+
"react": "^18 || ^19"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"@tanstack/react-query": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
52
|
+
"react": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
38
56
|
"devDependencies": {
|
|
57
|
+
"@tanstack/react-query": "^5.90.2",
|
|
58
|
+
"@testing-library/react": "^16.1.0",
|
|
39
59
|
"@types/chai": "^5.2.2",
|
|
40
60
|
"@types/chai-as-promised": "^7.1.8",
|
|
41
61
|
"@types/mocha": "^10.0.10",
|
|
42
62
|
"@types/node": "^25.6.0",
|
|
63
|
+
"@types/react": "^18.3.17",
|
|
64
|
+
"@types/react-dom": "^18.3.5",
|
|
43
65
|
"@types/sinon": "^17.0.4",
|
|
44
66
|
"chai": "^5.2.1",
|
|
45
67
|
"chai-as-promised": "^8.0.2",
|
|
68
|
+
"global-jsdom": "^25.0.0",
|
|
69
|
+
"jsdom": "^25.0.1",
|
|
46
70
|
"mocha": "^11.1.0",
|
|
71
|
+
"react": "^18.3.1",
|
|
72
|
+
"react-dom": "^18.3.1",
|
|
47
73
|
"sinon": "^21.0.0",
|
|
48
74
|
"tsx": "^4.19.3",
|
|
49
75
|
"typescript": "^5.8.3"
|