akanjs 3.0.0-alpha.44 → 3.0.0-alpha.45
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/fetch/client/fetchClient.ts +31 -8
- package/fetch/client/wsClient.ts +18 -10
- package/fetch/fetchType/endpointFetch.type.ts +1 -1
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/package.json +1 -1
- package/service/predefinedAdaptor/database.adaptor.ts +2 -0
- package/types/fetch/fetchType/endpointFetch.type.d.ts +1 -1
|
@@ -81,6 +81,7 @@ export class FetchClient {
|
|
|
81
81
|
readonly handler: Record<string, FetchHandler>;
|
|
82
82
|
readonly slice: Record<string, SliceMeta> = {};
|
|
83
83
|
readonly sortKeyMap = new Map<string, string[]>();
|
|
84
|
+
readonly #originWs = new Map<string, WsClient>();
|
|
84
85
|
readonly #handlerStore: Record<string, FetchHandler> = {};
|
|
85
86
|
readonly #handlerFactory = new Map<string, FetchHandlerFactory>();
|
|
86
87
|
#sharedRegistryAppliedVersion = 0;
|
|
@@ -95,8 +96,7 @@ export class FetchClient {
|
|
|
95
96
|
) {
|
|
96
97
|
this.origin = origin;
|
|
97
98
|
this.http = new HttpClient(origin, ErrorCls);
|
|
98
|
-
|
|
99
|
-
this.ws = new WsClient(wsUri, ErrorCls);
|
|
99
|
+
this.ws = new WsClient(FetchClient.#makeWsUri(origin), ErrorCls);
|
|
100
100
|
Object.assign(this.#handlerStore, handler);
|
|
101
101
|
this.handler = this.#makeHandlerProxy();
|
|
102
102
|
this.applySignal(serializedSignal);
|
|
@@ -158,6 +158,7 @@ export class FetchClient {
|
|
|
158
158
|
this.ErrorCls = ErrorCls;
|
|
159
159
|
this.http.setErrorConstructor(ErrorCls);
|
|
160
160
|
this.ws.setErrorConstructor(ErrorCls);
|
|
161
|
+
for (const ws of this.#originWs.values()) ws.setErrorConstructor(ErrorCls);
|
|
161
162
|
}
|
|
162
163
|
applySignal(serializedSignal: { [key: string]: SerializedSignal }, { share = true }: { share?: boolean } = {}) {
|
|
163
164
|
if (share && Object.keys(serializedSignal).length > 0) {
|
|
@@ -232,6 +233,8 @@ export class FetchClient {
|
|
|
232
233
|
}
|
|
233
234
|
disconnect() {
|
|
234
235
|
this.ws.destroy();
|
|
236
|
+
for (const ws of this.#originWs.values()) ws.destroy();
|
|
237
|
+
this.#originWs.clear();
|
|
235
238
|
}
|
|
236
239
|
clone({ origin, connect = true, jwt }: { origin?: string; connect?: boolean; jwt?: string } = {}) {
|
|
237
240
|
const instance = new FetchClient(origin ?? this.origin, {}, this.serializedSignal, this.ErrorCls);
|
|
@@ -245,6 +248,20 @@ export class FetchClient {
|
|
|
245
248
|
setJwt(jwt: string | null) {
|
|
246
249
|
this.jwt = jwt;
|
|
247
250
|
this.ws.setJwt(jwt);
|
|
251
|
+
for (const ws of this.#originWs.values()) ws.setJwt(jwt);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#resolveWs(origin?: string) {
|
|
255
|
+
if (!origin) return this.ws;
|
|
256
|
+
const target = origin.replace(/\/+$/, "");
|
|
257
|
+
if (target === this.origin.replace(/\/+$/, "")) return this.ws;
|
|
258
|
+
const cached = this.#originWs.get(target);
|
|
259
|
+
if (cached) return cached;
|
|
260
|
+
const ws = new WsClient(FetchClient.#makeWsUri(target), this.ErrorCls);
|
|
261
|
+
this.#originWs.set(target, ws);
|
|
262
|
+
ws.setJwt(this.jwt);
|
|
263
|
+
ws.connect();
|
|
264
|
+
return ws;
|
|
248
265
|
}
|
|
249
266
|
#makeAuthHeaders(option?: FetchPolicy): Record<string, string> {
|
|
250
267
|
if (option?.token) return { Authorization: `Bearer ${option.token}` };
|
|
@@ -338,13 +355,13 @@ export class FetchClient {
|
|
|
338
355
|
handleEvent(parsedReturn);
|
|
339
356
|
};
|
|
340
357
|
wrappedListeners.set(handleEvent, wrapped);
|
|
341
|
-
this
|
|
358
|
+
const ws = this.#resolveWs(fetchPolicy?.origin);
|
|
359
|
+
ws.subscribe({
|
|
342
360
|
key,
|
|
343
361
|
data,
|
|
344
362
|
handleEvent: wrapped,
|
|
345
363
|
});
|
|
346
|
-
return () =>
|
|
347
|
-
this.ws.unsubscribe({ key, data, handleEvent: wrappedListeners.get(handleEvent) ?? handleEvent });
|
|
364
|
+
return () => ws.unsubscribe({ key, data, handleEvent: wrappedListeners.get(handleEvent) ?? handleEvent });
|
|
348
365
|
};
|
|
349
366
|
});
|
|
350
367
|
return;
|
|
@@ -356,8 +373,9 @@ export class FetchClient {
|
|
|
356
373
|
const serializerMap = this.#makeArgSerializer(endpoint.args);
|
|
357
374
|
return (...argData: unknown[]) => {
|
|
358
375
|
const args = argData.slice(0, msgArgLength);
|
|
376
|
+
const fetchPolicy = argData[msgArgLength] as FetchPolicy | undefined;
|
|
359
377
|
const data = msgArgs.map((arg, idx) => serializerMap.get(arg.name)?.(args[idx]) ?? null);
|
|
360
|
-
this.
|
|
378
|
+
this.#resolveWs(fetchPolicy?.origin).emit(key, data);
|
|
361
379
|
};
|
|
362
380
|
});
|
|
363
381
|
this.#setHandlerFactory(`listen${capitalize(key)}`, () => {
|
|
@@ -369,8 +387,9 @@ export class FetchClient {
|
|
|
369
387
|
handleEvent(parsedReturn);
|
|
370
388
|
};
|
|
371
389
|
wrappedListeners.set(handleEvent, wrapped);
|
|
372
|
-
this.
|
|
373
|
-
|
|
390
|
+
const ws = this.#resolveWs(fetchPolicy.origin);
|
|
391
|
+
ws.on(key, wrapped);
|
|
392
|
+
return () => ws.off(key, wrappedListeners.get(handleEvent) ?? handleEvent);
|
|
374
393
|
}) as FetchHandler;
|
|
375
394
|
});
|
|
376
395
|
return;
|
|
@@ -380,6 +399,10 @@ export class FetchClient {
|
|
|
380
399
|
break;
|
|
381
400
|
}
|
|
382
401
|
}
|
|
402
|
+
static #makeWsUri(origin: string) {
|
|
403
|
+
return `${origin.replace("http://", "ws://").replace("https://", "wss://")}/ws`;
|
|
404
|
+
}
|
|
405
|
+
|
|
383
406
|
static paginationArgs: SerializedArg[] = [
|
|
384
407
|
{ type: "search", name: "skip", refName: "Int" },
|
|
385
408
|
{ type: "search", name: "limit", refName: "Int" },
|
package/fetch/client/wsClient.ts
CHANGED
|
@@ -41,6 +41,7 @@ export class WsClient {
|
|
|
41
41
|
#listenerMap = new Map<string, Set<Listener>>();
|
|
42
42
|
#destroyed = false;
|
|
43
43
|
#connectRequested = false;
|
|
44
|
+
#outbox: string[] = [];
|
|
44
45
|
#unconnectedWarnTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
45
46
|
#jwt: string | null = null;
|
|
46
47
|
connected = false;
|
|
@@ -94,6 +95,9 @@ export class WsClient {
|
|
|
94
95
|
const data: WebsocketReqData = { key: option.key, data: option.data, subscribe: true };
|
|
95
96
|
this.#ws?.send(JSON.stringify(data));
|
|
96
97
|
});
|
|
98
|
+
const queued = this.#outbox;
|
|
99
|
+
this.#outbox = [];
|
|
100
|
+
for (const frame of queued) this.#ws?.send(frame);
|
|
97
101
|
};
|
|
98
102
|
this.#ws.onmessage = (e) => {
|
|
99
103
|
try {
|
|
@@ -197,6 +201,7 @@ export class WsClient {
|
|
|
197
201
|
}
|
|
198
202
|
for (const timer of this.#unconnectedWarnTimers.values()) clearTimeout(timer);
|
|
199
203
|
this.#unconnectedWarnTimers.clear();
|
|
204
|
+
this.#outbox = [];
|
|
200
205
|
this.#ws?.close();
|
|
201
206
|
this.#ws = null;
|
|
202
207
|
}
|
|
@@ -240,28 +245,31 @@ export class WsClient {
|
|
|
240
245
|
`[akanjs] WebSocket is not connected. Call fetch.instance.connect(), or drop the root layout "wsConnect = false", before ${action} "${key}".`,
|
|
241
246
|
);
|
|
242
247
|
}
|
|
243
|
-
#
|
|
244
|
-
|
|
248
|
+
#warnUnconnected(action: "emit" | "subscribe", key: string) {
|
|
249
|
+
const timerKey = `${action}:${key}`;
|
|
250
|
+
if (this.#connectRequested || this.#unconnectedWarnTimers.has(timerKey)) return;
|
|
245
251
|
const timer = setTimeout(() => {
|
|
246
|
-
this.#unconnectedWarnTimers.delete(
|
|
252
|
+
this.#unconnectedWarnTimers.delete(timerKey);
|
|
247
253
|
if (this.#connectRequested || this.#destroyed) return;
|
|
248
|
-
this.#warnNotConnected(
|
|
254
|
+
this.#warnNotConnected(action, key);
|
|
249
255
|
}, 0);
|
|
250
|
-
this.#unconnectedWarnTimers.set(
|
|
256
|
+
this.#unconnectedWarnTimers.set(timerKey, timer);
|
|
251
257
|
}
|
|
252
258
|
emit(key: string, data: WsRequestPayload) {
|
|
259
|
+
const payload: WebsocketReqData = { key, data: Array.isArray(data) ? data : [data] };
|
|
260
|
+
const frame = JSON.stringify(payload);
|
|
261
|
+
|
|
253
262
|
if (this.#ws?.readyState !== WebSocket.OPEN) {
|
|
254
|
-
this.
|
|
255
|
-
this.#
|
|
263
|
+
this.#outbox.push(frame);
|
|
264
|
+
this.#warnUnconnected("emit", key);
|
|
256
265
|
return this;
|
|
257
266
|
}
|
|
258
|
-
|
|
259
|
-
this.#ws.send(JSON.stringify(payload));
|
|
267
|
+
this.#ws.send(frame);
|
|
260
268
|
return this;
|
|
261
269
|
}
|
|
262
270
|
subscribe(option: { key: string; data: unknown[]; handleEvent: (data: unknown) => void }) {
|
|
263
271
|
const roomId = WsClient.makeRoomId(option.key, option.data);
|
|
264
|
-
if (!this.#ws) this.#
|
|
272
|
+
if (!this.#ws) this.#warnUnconnected("subscribe", option.key);
|
|
265
273
|
if (!this.#roomSubscribeMap.has(roomId)) {
|
|
266
274
|
this.#roomSubscribeMap.set(roomId, { key: option.key, data: option.data, listener: new Set() });
|
|
267
275
|
if (this.#ws?.readyState === WebSocket.OPEN) {
|
|
@@ -44,7 +44,7 @@ type QueryOrMutationFetchFn<E, SlceCls extends SliceCls | never> = (
|
|
|
44
44
|
/** Typed off `PromptResult` rather than the endpoint's return ref, which is the `Any` carrier a prompt rides on. */
|
|
45
45
|
type PromptFetchFn<E> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => Promise<PromptMessage[]>;
|
|
46
46
|
|
|
47
|
-
type MessageEmitFn<E> = (...args: EndpInfoArgs<E
|
|
47
|
+
type MessageEmitFn<E> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => void;
|
|
48
48
|
|
|
49
49
|
type MessageListenFn<E, SlceCls extends SliceCls | never> = (
|
|
50
50
|
handleEvent: (data: EndpInfoReturns<E, SlceCls>) => PromiseOrObject<void>,
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type DocumentUpdateOptions,
|
|
19
19
|
documentQueryHelper,
|
|
20
20
|
encodeDocumentValue,
|
|
21
|
+
isDocumentId,
|
|
21
22
|
isDocumentUpdateNode,
|
|
22
23
|
NoDocumentError,
|
|
23
24
|
resolveDocumentUpdate,
|
|
@@ -1033,6 +1034,7 @@ export class SqlDocumentStore {
|
|
|
1033
1034
|
async create(data: DocumentRecord, { runSaveHooks = true }: WriteHookOptions = {}) {
|
|
1034
1035
|
const now = Date.now();
|
|
1035
1036
|
const id = data.id ?? createDocumentId(now);
|
|
1037
|
+
if (!isDocumentId(id)) throw new Error(`Invalid ID value: ${id}`);
|
|
1036
1038
|
const doc = this.hydrate(
|
|
1037
1039
|
this.prepareDocument({
|
|
1038
1040
|
...data,
|
|
@@ -7,7 +7,7 @@ type EndpInfoReturns<E, SlceCls extends SliceCls | never> = EndpointClientReturn
|
|
|
7
7
|
type QueryOrMutationFetchFn<E, SlceCls extends SliceCls | never> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => Promise<EndpInfoReturns<E, SlceCls>>;
|
|
8
8
|
/** Typed off `PromptResult` rather than the endpoint's return ref, which is the `Any` carrier a prompt rides on. */
|
|
9
9
|
type PromptFetchFn<E> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => Promise<PromptMessage[]>;
|
|
10
|
-
type MessageEmitFn<E> = (...args: EndpInfoArgs<E
|
|
10
|
+
type MessageEmitFn<E> = (...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]) => void;
|
|
11
11
|
type MessageListenFn<E, SlceCls extends SliceCls | never> = (handleEvent: (data: EndpInfoReturns<E, SlceCls>) => PromiseOrObject<void>, options?: FetchPolicy) => () => void;
|
|
12
12
|
type PubsubSubscribeFn<E, SlceCls extends SliceCls | never> = (...args: [
|
|
13
13
|
...EndpInfoArgs<E>,
|