@kevisual/query 0.0.2 → 0.0.3

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/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { adapter } from './adapter.ts';
2
+ export { QueryWs } from './ws.ts';
2
3
  type Fn = (opts: {
3
4
  url?: string;
4
5
  headers?: Record<string, string>;
@@ -15,20 +16,37 @@ type QueryOpts = {
15
16
  type Data = {
16
17
  path?: string;
17
18
  key?: string;
19
+ payload?: Record<string, any>;
18
20
  [key: string]: any;
19
21
  };
22
+ type Result<S = any> = {
23
+ code: number;
24
+ data?: S;
25
+ message?: string;
26
+ success: boolean;
27
+ };
20
28
  type DataOpts = Partial<QueryOpts> & {
21
29
  beforeRequest?: Fn;
30
+ afterResponse?: (result: Result) => Promise<any>;
22
31
  };
32
+ /**
33
+ * const query = new Query();
34
+ * const res = await query.post({
35
+ * path: 'demo',
36
+ * key: '1',
37
+ * });
38
+ */
23
39
  export declare class Query {
24
40
  adapter: typeof adapter;
25
41
  url: string;
26
42
  beforeRequest?: Fn;
43
+ afterResponse?: (result: Result) => Promise<any>;
27
44
  headers?: Record<string, string>;
28
45
  timeout?: number;
29
- constructor(opts: QueryOpts);
30
- get<T>(params: Record<string, any> & Data & T, options?: DataOpts): Promise<any>;
31
- post<T>(body: Record<string, any> & Data & T, options?: DataOpts): Promise<any>;
46
+ constructor(opts?: QueryOpts);
47
+ get<T, S>(params: Record<string, any> & Data & T, options?: DataOpts): Promise<Result<S>>;
48
+ post<T, S>(body: Record<string, any> & Data & T, options?: DataOpts): Promise<Result<S>>;
32
49
  before(fn: Fn): void;
50
+ after(fn: (result: Result) => Promise<any>): void;
33
51
  }
34
52
  export { adapter };
package/dist/index.js CHANGED
@@ -39,19 +39,194 @@ const adapter = async (opts) => {
39
39
  });
40
40
  };
41
41
 
42
+ const createStoreImpl = (createState) => {
43
+ let state;
44
+ const listeners = /* @__PURE__ */ new Set();
45
+ const setState = (partial, replace) => {
46
+ const nextState = typeof partial === "function" ? partial(state) : partial;
47
+ if (!Object.is(nextState, state)) {
48
+ const previousState = state;
49
+ state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
50
+ listeners.forEach((listener) => listener(state, previousState));
51
+ }
52
+ };
53
+ const getState = () => state;
54
+ const getInitialState = () => initialState;
55
+ const subscribe = (listener) => {
56
+ listeners.add(listener);
57
+ return () => listeners.delete(listener);
58
+ };
59
+ const destroy = () => {
60
+ if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
61
+ console.warn(
62
+ "[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."
63
+ );
64
+ }
65
+ listeners.clear();
66
+ };
67
+ const api = { setState, getState, getInitialState, subscribe, destroy };
68
+ const initialState = state = createState(setState, getState, api);
69
+ return api;
70
+ };
71
+ const createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
72
+
73
+ const parseWsUrl = (url) => {
74
+ try {
75
+ new URL(url);
76
+ return url;
77
+ }
78
+ catch (e) {
79
+ const _url = new URL(url, location.origin);
80
+ if (_url.protocol === 'http:') {
81
+ _url.protocol = 'ws:';
82
+ }
83
+ if (_url.protocol === 'https:') {
84
+ _url.protocol = 'wss:';
85
+ }
86
+ return _url.href;
87
+ }
88
+ };
89
+
90
+ class QueryWs {
91
+ url;
92
+ store;
93
+ ws;
94
+ constructor(opts) {
95
+ const url = opts?.url || '/api/router';
96
+ if (opts?.store) {
97
+ this.store = opts.store;
98
+ }
99
+ else {
100
+ const store = createStore((set) => ({
101
+ connected: false,
102
+ status: 'connecting',
103
+ setConnected: (connected) => set({ connected }),
104
+ setStatus: (status) => set({ status }),
105
+ }));
106
+ this.store = store;
107
+ }
108
+ const wsUrl = parseWsUrl(url);
109
+ if (opts?.ws && opts.ws instanceof WebSocket) {
110
+ this.ws = opts.ws;
111
+ }
112
+ else {
113
+ this.ws = new WebSocket(wsUrl);
114
+ }
115
+ this.connect();
116
+ }
117
+ /**
118
+ * 连接 WebSocket
119
+ */
120
+ connect() {
121
+ const store = this.store;
122
+ const connected = store.getState().connected;
123
+ if (connected) {
124
+ return;
125
+ }
126
+ const ws = this.ws || new WebSocket(this.url);
127
+ ws.onopen = () => {
128
+ store.getState().setConnected(true);
129
+ store.getState().setStatus('connected');
130
+ };
131
+ ws.onclose = () => {
132
+ store.getState().setConnected(false);
133
+ this.ws = null;
134
+ };
135
+ }
136
+ listenConnect(callback) {
137
+ const store = this.store;
138
+ const { connected } = store.getState();
139
+ if (connected) {
140
+ callback();
141
+ return;
142
+ }
143
+ const subscriptionOne = (selector, listener) => {
144
+ const unsubscribe = store.subscribe((newState, oldState) => {
145
+ if (selector(newState) !== selector(oldState)) {
146
+ listener(newState, oldState);
147
+ unsubscribe();
148
+ }
149
+ });
150
+ return unsubscribe;
151
+ };
152
+ const cancel = subscriptionOne((state) => state.connected, () => {
153
+ callback();
154
+ });
155
+ return cancel;
156
+ }
157
+ onMessage(fn, opts) {
158
+ const ws = this.ws;
159
+ const isJson = opts?.isJson ?? true;
160
+ const selector = opts?.selector;
161
+ const parseIfJson = (data) => {
162
+ try {
163
+ return JSON.parse(data);
164
+ }
165
+ catch (e) {
166
+ return data;
167
+ }
168
+ };
169
+ const listener = (event) => {
170
+ const received = parseIfJson(event.data);
171
+ if (typeof received === 'string' && !isJson) {
172
+ fn(received, event);
173
+ }
174
+ else if (typeof received === 'object' && isJson) {
175
+ fn(selector ? selector(received) : received, event);
176
+ }
177
+ else ;
178
+ };
179
+ ws.addEventListener('message', listener);
180
+ return () => {
181
+ ws.removeEventListener('message', listener);
182
+ };
183
+ }
184
+ close() {
185
+ const ws = this.ws;
186
+ const store = this.store;
187
+ ws?.close?.();
188
+ this.ws = null;
189
+ store.getState().setConnected(false);
190
+ store.getState().setStatus('disconnected');
191
+ }
192
+ send(data, opts) {
193
+ const ws = this.ws;
194
+ const isJson = opts?.isJson ?? true;
195
+ const wrapper = opts?.wrapper;
196
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
197
+ console.error('WebSocket is not open');
198
+ return;
199
+ }
200
+ if (isJson) {
201
+ ws.send(JSON.stringify(wrapper ? wrapper(data) : data));
202
+ }
203
+ else {
204
+ ws.send(data);
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * const query = new Query();
211
+ * const res = await query.post({
212
+ * path: 'demo',
213
+ * key: '1',
214
+ * });
215
+ */
42
216
  class Query {
43
217
  adapter;
44
218
  url;
45
219
  beforeRequest;
220
+ afterResponse;
46
221
  headers;
47
222
  timeout;
48
223
  constructor(opts) {
49
- this.adapter = opts.adapter || adapter;
50
- this.url = opts.url || '/api/router';
51
- this.headers = opts.headers || {
224
+ this.adapter = opts?.adapter || adapter;
225
+ this.url = opts?.url || '/api/router';
226
+ this.headers = opts?.headers || {
52
227
  'Content-Type': 'application/json',
53
228
  };
54
- this.timeout = opts.timeout || 60000; // 默认超时时间为 60s
229
+ this.timeout = opts?.timeout || 60000; // 默认超时时间为 60s
55
230
  }
56
231
  async get(params, options) {
57
232
  return this.post(params, options);
@@ -71,11 +246,20 @@ class Query {
71
246
  if (beforeRequest) {
72
247
  await beforeRequest(req);
73
248
  }
74
- return adapter(req);
249
+ return adapter(req).then(async (res) => {
250
+ res.success = res.code === 200;
251
+ if (options?.afterResponse) {
252
+ return await options.afterResponse(res);
253
+ }
254
+ return res;
255
+ });
75
256
  }
76
257
  before(fn) {
77
258
  this.beforeRequest = fn;
78
259
  }
260
+ after(fn) {
261
+ this.afterResponse = fn;
262
+ }
79
263
  }
80
264
 
81
- export { Query, adapter };
265
+ export { Query, QueryWs, adapter };
@@ -0,0 +1,2 @@
1
+ export declare const parseUrl: (url: string) => string;
2
+ export declare const parseWsUrl: (url: string) => string;
package/dist/ws.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { StoreApi } from 'zustand/vanilla';
2
+ type QueryWsStore = {
3
+ connected: boolean;
4
+ status: 'connecting' | 'connected' | 'disconnected';
5
+ setConnected: (connected: boolean) => void;
6
+ setStatus: (status: QuerySelectState) => void;
7
+ };
8
+ export type QuerySelectState = 'connecting' | 'connected' | 'disconnected';
9
+ export type QueryWsStoreListener = (newState: QueryWsStore, oldState: QueryWsStore) => void;
10
+ type QueryWsOpts = {
11
+ url?: string;
12
+ store?: StoreApi<QueryWsStore>;
13
+ ws?: WebSocket;
14
+ };
15
+ export type WsSend<T = any, U = any> = (data: T, opts?: {
16
+ isJson?: boolean;
17
+ wrapper?: (data: T) => U;
18
+ }) => any;
19
+ export type WsOnMessage<T = any, U = any> = (fn: (data: U, event: MessageEvent) => void, opts?: {
20
+ isJson?: boolean;
21
+ selector?: (data: T) => U;
22
+ }) => any;
23
+ export declare class QueryWs {
24
+ url: string;
25
+ store: StoreApi<QueryWsStore>;
26
+ ws: WebSocket;
27
+ constructor(opts?: QueryWsOpts);
28
+ /**
29
+ * 连接 WebSocket
30
+ */
31
+ connect(): void;
32
+ listenConnect(callback: () => void): () => void;
33
+ onMessage<T = any, U = any>(fn: (data: U, event: MessageEvent) => void, opts?: {
34
+ isJson?: boolean;
35
+ selector?: (data: T) => U;
36
+ }): () => void;
37
+ close(): void;
38
+ send<T = any, U = any>(data: T, opts?: {
39
+ isJson?: boolean;
40
+ wrapper?: (data: T) => U;
41
+ }): void;
42
+ }
43
+ export {};
package/dist/ws.js ADDED
@@ -0,0 +1,168 @@
1
+ const createStoreImpl = (createState) => {
2
+ let state;
3
+ const listeners = /* @__PURE__ */ new Set();
4
+ const setState = (partial, replace) => {
5
+ const nextState = typeof partial === "function" ? partial(state) : partial;
6
+ if (!Object.is(nextState, state)) {
7
+ const previousState = state;
8
+ state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
9
+ listeners.forEach((listener) => listener(state, previousState));
10
+ }
11
+ };
12
+ const getState = () => state;
13
+ const getInitialState = () => initialState;
14
+ const subscribe = (listener) => {
15
+ listeners.add(listener);
16
+ return () => listeners.delete(listener);
17
+ };
18
+ const destroy = () => {
19
+ if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production") {
20
+ console.warn(
21
+ "[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."
22
+ );
23
+ }
24
+ listeners.clear();
25
+ };
26
+ const api = { setState, getState, getInitialState, subscribe, destroy };
27
+ const initialState = state = createState(setState, getState, api);
28
+ return api;
29
+ };
30
+ const createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
31
+
32
+ const parseWsUrl = (url) => {
33
+ try {
34
+ new URL(url);
35
+ return url;
36
+ }
37
+ catch (e) {
38
+ const _url = new URL(url, location.origin);
39
+ if (_url.protocol === 'http:') {
40
+ _url.protocol = 'ws:';
41
+ }
42
+ if (_url.protocol === 'https:') {
43
+ _url.protocol = 'wss:';
44
+ }
45
+ return _url.href;
46
+ }
47
+ };
48
+
49
+ class QueryWs {
50
+ url;
51
+ store;
52
+ ws;
53
+ constructor(opts) {
54
+ const url = opts?.url || '/api/router';
55
+ if (opts?.store) {
56
+ this.store = opts.store;
57
+ }
58
+ else {
59
+ const store = createStore((set) => ({
60
+ connected: false,
61
+ status: 'connecting',
62
+ setConnected: (connected) => set({ connected }),
63
+ setStatus: (status) => set({ status }),
64
+ }));
65
+ this.store = store;
66
+ }
67
+ const wsUrl = parseWsUrl(url);
68
+ if (opts?.ws && opts.ws instanceof WebSocket) {
69
+ this.ws = opts.ws;
70
+ }
71
+ else {
72
+ this.ws = new WebSocket(wsUrl);
73
+ }
74
+ this.connect();
75
+ }
76
+ /**
77
+ * 连接 WebSocket
78
+ */
79
+ connect() {
80
+ const store = this.store;
81
+ const connected = store.getState().connected;
82
+ if (connected) {
83
+ return;
84
+ }
85
+ const ws = this.ws || new WebSocket(this.url);
86
+ ws.onopen = () => {
87
+ store.getState().setConnected(true);
88
+ store.getState().setStatus('connected');
89
+ };
90
+ ws.onclose = () => {
91
+ store.getState().setConnected(false);
92
+ this.ws = null;
93
+ };
94
+ }
95
+ listenConnect(callback) {
96
+ const store = this.store;
97
+ const { connected } = store.getState();
98
+ if (connected) {
99
+ callback();
100
+ return;
101
+ }
102
+ const subscriptionOne = (selector, listener) => {
103
+ const unsubscribe = store.subscribe((newState, oldState) => {
104
+ if (selector(newState) !== selector(oldState)) {
105
+ listener(newState, oldState);
106
+ unsubscribe();
107
+ }
108
+ });
109
+ return unsubscribe;
110
+ };
111
+ const cancel = subscriptionOne((state) => state.connected, () => {
112
+ callback();
113
+ });
114
+ return cancel;
115
+ }
116
+ onMessage(fn, opts) {
117
+ const ws = this.ws;
118
+ const isJson = opts?.isJson ?? true;
119
+ const selector = opts?.selector;
120
+ const parseIfJson = (data) => {
121
+ try {
122
+ return JSON.parse(data);
123
+ }
124
+ catch (e) {
125
+ return data;
126
+ }
127
+ };
128
+ const listener = (event) => {
129
+ const received = parseIfJson(event.data);
130
+ if (typeof received === 'string' && !isJson) {
131
+ fn(received, event);
132
+ }
133
+ else if (typeof received === 'object' && isJson) {
134
+ fn(selector ? selector(received) : received, event);
135
+ }
136
+ else ;
137
+ };
138
+ ws.addEventListener('message', listener);
139
+ return () => {
140
+ ws.removeEventListener('message', listener);
141
+ };
142
+ }
143
+ close() {
144
+ const ws = this.ws;
145
+ const store = this.store;
146
+ ws?.close?.();
147
+ this.ws = null;
148
+ store.getState().setConnected(false);
149
+ store.getState().setStatus('disconnected');
150
+ }
151
+ send(data, opts) {
152
+ const ws = this.ws;
153
+ const isJson = opts?.isJson ?? true;
154
+ const wrapper = opts?.wrapper;
155
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
156
+ console.error('WebSocket is not open');
157
+ return;
158
+ }
159
+ if (isJson) {
160
+ ws.send(JSON.stringify(wrapper ? wrapper(data) : data));
161
+ }
162
+ else {
163
+ ws.send(data);
164
+ }
165
+ }
166
+ }
167
+
168
+ export { QueryWs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevisual/query",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,17 +21,28 @@
21
21
  "devDependencies": {
22
22
  "@rollup/plugin-node-resolve": "^15.2.3",
23
23
  "@rollup/plugin-typescript": "^11.1.6",
24
- "@types/jest": "^29.5.12",
25
- "jest": "^29.7.0",
26
- "jest-config": "^29.7.0",
27
24
  "rollup": "^4.21.2",
28
- "ts-jest": "^29.2.5",
29
25
  "ts-node": "^10.9.2",
30
26
  "tslib": "^2.7.0",
27
+ "zustand": "^4.5.5",
31
28
  "typescript": "^5.5.4"
32
29
  },
33
30
  "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447",
34
31
  "publishConfig": {
35
32
  "access": "public"
33
+ },
34
+ "exports": {
35
+ ".": {
36
+ "import": "./dist/index.js",
37
+ "require": "./dist/index.js"
38
+ },
39
+ "./node": {
40
+ "import": "./dist/node-adapter.js",
41
+ "require": "./dist/node-adapter.js"
42
+ },
43
+ "./ws": {
44
+ "import": "./dist/ws.js",
45
+ "require": "./dist/ws.js"
46
+ }
36
47
  }
37
- }
48
+ }