@kevisual/query 0.0.2-alpha.0 → 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>;
@@ -42,7 +43,7 @@ export declare class Query {
42
43
  afterResponse?: (result: Result) => Promise<any>;
43
44
  headers?: Record<string, string>;
44
45
  timeout?: number;
45
- constructor(opts: QueryOpts);
46
+ constructor(opts?: QueryOpts);
46
47
  get<T, S>(params: Record<string, any> & Data & T, options?: DataOpts): Promise<Result<S>>;
47
48
  post<T, S>(body: Record<string, any> & Data & T, options?: DataOpts): Promise<Result<S>>;
48
49
  before(fn: Fn): void;
package/dist/index.js CHANGED
@@ -39,6 +39,173 @@ 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
+
42
209
  /**
43
210
  * const query = new Query();
44
211
  * const res = await query.post({
@@ -54,12 +221,12 @@ class Query {
54
221
  headers;
55
222
  timeout;
56
223
  constructor(opts) {
57
- this.adapter = opts.adapter || adapter;
58
- this.url = opts.url || '/api/router';
59
- this.headers = opts.headers || {
224
+ this.adapter = opts?.adapter || adapter;
225
+ this.url = opts?.url || '/api/router';
226
+ this.headers = opts?.headers || {
60
227
  'Content-Type': 'application/json',
61
228
  };
62
- this.timeout = opts.timeout || 60000; // 默认超时时间为 60s
229
+ this.timeout = opts?.timeout || 60000; // 默认超时时间为 60s
63
230
  }
64
231
  async get(params, options) {
65
232
  return this.post(params, options);
@@ -95,4 +262,4 @@ class Query {
95
262
  }
96
263
  }
97
264
 
98
- 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-alpha.0",
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
+ }