akanjs 2.2.6 → 2.2.7-rc.1
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.
|
@@ -17,6 +17,7 @@ import { type ErrorConstructor, HttpClient } from "./httpClient";
|
|
|
17
17
|
import { WsClient } from "./wsClient";
|
|
18
18
|
|
|
19
19
|
type FetchHandler = (...args: unknown[]) => PromiseOrObject<unknown>;
|
|
20
|
+
type FetchHandlerFactory = () => FetchHandler;
|
|
20
21
|
type UnknownRecord = Record<string, unknown>;
|
|
21
22
|
|
|
22
23
|
const isNullableArg = (arg: SerializedArg) => arg.nullable ?? arg.type === "search";
|
|
@@ -46,13 +47,18 @@ type ClientSignalMap<SigType extends { fetch: any }> = {
|
|
|
46
47
|
|
|
47
48
|
/** Runtime fetch client that registers serialized Akan signals as HTTP/WebSocket methods. */
|
|
48
49
|
export class FetchClient {
|
|
50
|
+
static #sharedSerializedSignal: { [key: string]: SerializedSignal } = {};
|
|
51
|
+
static #sharedRegistryVersion = 0;
|
|
49
52
|
readonly logger = new Logger("FetchClient");
|
|
50
53
|
readonly origin: string;
|
|
51
54
|
readonly http: HttpClient;
|
|
52
55
|
readonly ws: WsClient;
|
|
53
|
-
readonly handler: Record<string, FetchHandler
|
|
56
|
+
readonly handler: Record<string, FetchHandler>;
|
|
54
57
|
readonly slice: Record<string, SliceMeta> = {};
|
|
55
58
|
readonly sortKeyMap = new Map<string, string[]>();
|
|
59
|
+
readonly #handlerStore: Record<string, FetchHandler> = {};
|
|
60
|
+
readonly #handlerFactory = new Map<string, FetchHandlerFactory>();
|
|
61
|
+
#sharedRegistryAppliedVersion = 0;
|
|
56
62
|
serializedSignal: { [key: string]: SerializedSignal } = {};
|
|
57
63
|
jwt: string | null = null;
|
|
58
64
|
|
|
@@ -66,16 +72,52 @@ export class FetchClient {
|
|
|
66
72
|
this.http = new HttpClient(origin, ErrorCls);
|
|
67
73
|
const wsUri = `${origin.replace("http://", "ws://").replace("https://", "wss://")}/ws`;
|
|
68
74
|
this.ws = new WsClient(wsUri, ErrorCls);
|
|
69
|
-
this
|
|
75
|
+
Object.assign(this.#handlerStore, handler);
|
|
76
|
+
this.handler = this.#makeHandlerProxy();
|
|
70
77
|
this.applySignal(serializedSignal);
|
|
71
78
|
}
|
|
79
|
+
static resetSharedRegistry() {
|
|
80
|
+
FetchClient.#sharedSerializedSignal = {};
|
|
81
|
+
FetchClient.#sharedRegistryVersion++;
|
|
82
|
+
}
|
|
83
|
+
static #mergeSerializedSignalInto(
|
|
84
|
+
serializedSignal: { [key: string]: SerializedSignal },
|
|
85
|
+
refName: string,
|
|
86
|
+
signal: SerializedSignal,
|
|
87
|
+
) {
|
|
88
|
+
const current = serializedSignal[refName];
|
|
89
|
+
serializedSignal[refName] = current
|
|
90
|
+
? {
|
|
91
|
+
...current,
|
|
92
|
+
...signal,
|
|
93
|
+
endpoint: { ...current.endpoint, ...signal.endpoint },
|
|
94
|
+
slice: current.slice || signal.slice ? { ...current.slice, ...signal.slice } : undefined,
|
|
95
|
+
filter:
|
|
96
|
+
current.filter || signal.filter
|
|
97
|
+
? {
|
|
98
|
+
filter: { ...current.filter?.filter, ...signal.filter?.filter },
|
|
99
|
+
sortKeys: [...new Set([...(current.filter?.sortKeys ?? []), ...(signal.filter?.sortKeys ?? [])])],
|
|
100
|
+
}
|
|
101
|
+
: undefined,
|
|
102
|
+
getGuards: signal.getGuards ?? current.getGuards,
|
|
103
|
+
cruGuards: signal.cruGuards ?? current.cruGuards,
|
|
104
|
+
}
|
|
105
|
+
: signal;
|
|
106
|
+
}
|
|
72
107
|
setErrorConstructor(ErrorCls?: ErrorConstructor) {
|
|
73
108
|
this.ErrorCls = ErrorCls;
|
|
74
109
|
this.http.setErrorConstructor(ErrorCls);
|
|
75
110
|
this.ws.setErrorConstructor(ErrorCls);
|
|
76
111
|
}
|
|
77
|
-
applySignal(serializedSignal: { [key: string]: SerializedSignal }) {
|
|
78
|
-
Object.
|
|
112
|
+
applySignal(serializedSignal: { [key: string]: SerializedSignal }, { share = true }: { share?: boolean } = {}) {
|
|
113
|
+
if (share && Object.keys(serializedSignal).length > 0) {
|
|
114
|
+
for (const [refName, signal] of Object.entries(serializedSignal))
|
|
115
|
+
FetchClient.#mergeSerializedSignalInto(FetchClient.#sharedSerializedSignal, refName, signal);
|
|
116
|
+
FetchClient.#sharedRegistryVersion++;
|
|
117
|
+
this.#sharedRegistryAppliedVersion = FetchClient.#sharedRegistryVersion;
|
|
118
|
+
}
|
|
119
|
+
for (const [refName, signal] of Object.entries(serializedSignal))
|
|
120
|
+
FetchClient.#mergeSerializedSignalInto(this.serializedSignal, refName, signal);
|
|
79
121
|
for (const [refName, signal] of Object.entries(serializedSignal)) {
|
|
80
122
|
for (const [key, endpoint] of Object.entries(signal.endpoint))
|
|
81
123
|
this.#registerEndpoint(key, endpoint, signal.prefix);
|
|
@@ -93,6 +135,44 @@ export class FetchClient {
|
|
|
93
135
|
}
|
|
94
136
|
return this;
|
|
95
137
|
}
|
|
138
|
+
#makeHandlerProxy() {
|
|
139
|
+
return new Proxy(this.#handlerStore, {
|
|
140
|
+
get: (target, prop) => {
|
|
141
|
+
if (typeof prop !== "string") return undefined;
|
|
142
|
+
return target[prop] ?? this.#getOrCreateHandler(prop);
|
|
143
|
+
},
|
|
144
|
+
has: (target, prop) => {
|
|
145
|
+
if (typeof prop !== "string") return prop in target;
|
|
146
|
+
return prop in target || this.#handlerFactory.has(prop);
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
#syncSharedRegistry() {
|
|
151
|
+
if (this.#sharedRegistryAppliedVersion === FetchClient.#sharedRegistryVersion) return;
|
|
152
|
+
this.applySignal(FetchClient.#sharedSerializedSignal, { share: false });
|
|
153
|
+
this.#sharedRegistryAppliedVersion = FetchClient.#sharedRegistryVersion;
|
|
154
|
+
}
|
|
155
|
+
#getOrCreateHandler(key: string): FetchHandler | undefined {
|
|
156
|
+
const current = this.#handlerStore[key];
|
|
157
|
+
if (current) return current;
|
|
158
|
+
const factory = this.#handlerFactory.get(key);
|
|
159
|
+
if (factory) {
|
|
160
|
+
const handler = factory();
|
|
161
|
+
this.#handlerStore[key] = handler;
|
|
162
|
+
return handler;
|
|
163
|
+
}
|
|
164
|
+
this.#syncSharedRegistry();
|
|
165
|
+
const syncedFactory = this.#handlerFactory.get(key);
|
|
166
|
+
if (!syncedFactory) return undefined;
|
|
167
|
+
const handler = syncedFactory();
|
|
168
|
+
this.#handlerStore[key] = handler;
|
|
169
|
+
return handler;
|
|
170
|
+
}
|
|
171
|
+
#requireHandler<T extends FetchHandler = FetchHandler>(key: string, owner: string): T {
|
|
172
|
+
const handler = this.#getOrCreateHandler(key);
|
|
173
|
+
if (!handler) throw new Error(`${owner} requires fetch handler "${key}", but it is not registered`);
|
|
174
|
+
return handler as T;
|
|
175
|
+
}
|
|
96
176
|
connect() {
|
|
97
177
|
this.ws.connect();
|
|
98
178
|
}
|
|
@@ -176,58 +256,66 @@ export class FetchClient {
|
|
|
176
256
|
#registerEndpoint(key: string, endpoint: SerializedEndpoint, prefix?: string) {
|
|
177
257
|
switch (endpoint.type) {
|
|
178
258
|
case "query": {
|
|
179
|
-
|
|
259
|
+
this.#handlerFactory.set(key, () => this.#makeHttpFn(key, endpoint, prefix));
|
|
260
|
+
return;
|
|
180
261
|
}
|
|
181
262
|
case "mutation": {
|
|
182
|
-
|
|
263
|
+
this.#handlerFactory.set(key, () => this.#makeHttpFn(key, endpoint, prefix));
|
|
264
|
+
return;
|
|
183
265
|
}
|
|
184
266
|
case "pubsub": {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
267
|
+
this.#handlerFactory.set(`subscribe${capitalize(key)}`, () => {
|
|
268
|
+
const roomArgs = endpoint.args.filter((arg) => arg.type === "room");
|
|
269
|
+
const roomArgLength = roomArgs.length;
|
|
270
|
+
const serializerMap = this.#makeArgSerializer(endpoint.args);
|
|
271
|
+
const parseReturn = this.#makeReturnParser(endpoint.returns);
|
|
272
|
+
const wrappedListeners = new WeakMap<(data: unknown) => void, (data: unknown) => void>();
|
|
273
|
+
return async (...argData: unknown[]) => {
|
|
274
|
+
const args = argData.slice(0, roomArgLength);
|
|
275
|
+
const handleEvent = argData[roomArgLength] as (data: unknown) => void;
|
|
276
|
+
const fetchPolicy = argData[roomArgLength + 1] as FetchPolicy | undefined;
|
|
277
|
+
const data = roomArgs.map((arg, idx) => serializerMap.get(arg.name)?.(args[idx]) ?? null);
|
|
278
|
+
const wrapped = (data: unknown) => {
|
|
279
|
+
const parsedReturn = parseReturn(data, { crystalize: fetchPolicy?.crystalize ?? true });
|
|
280
|
+
handleEvent(parsedReturn);
|
|
281
|
+
};
|
|
282
|
+
wrappedListeners.set(handleEvent, wrapped);
|
|
283
|
+
this.ws.subscribe({
|
|
284
|
+
key,
|
|
285
|
+
data,
|
|
286
|
+
handleEvent: wrapped,
|
|
287
|
+
});
|
|
288
|
+
return () =>
|
|
289
|
+
this.ws.unsubscribe({ key, data, handleEvent: wrappedListeners.get(handleEvent) ?? handleEvent });
|
|
198
290
|
};
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
key,
|
|
202
|
-
data,
|
|
203
|
-
handleEvent: wrapped,
|
|
204
|
-
});
|
|
205
|
-
return () =>
|
|
206
|
-
this.ws.unsubscribe({ key, data, handleEvent: wrappedListeners.get(handleEvent) ?? handleEvent });
|
|
207
|
-
};
|
|
208
|
-
return Object.assign(this.handler, { [`subscribe${capitalize(key)}`]: pubsubFn });
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
209
293
|
}
|
|
210
294
|
case "message": {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
this.ws.emit(key, data);
|
|
220
|
-
};
|
|
221
|
-
const listenFn = (handleEvent: (data: unknown) => void, fetchPolicy: FetchPolicy = {}) => {
|
|
222
|
-
const wrapped = (data: unknown) => {
|
|
223
|
-
const parsedReturn = parseReturn(data, { crystalize: fetchPolicy?.crystalize ?? true });
|
|
224
|
-
handleEvent(parsedReturn);
|
|
295
|
+
this.#handlerFactory.set(key, () => {
|
|
296
|
+
const msgArgs = endpoint.args.filter((arg) => arg.type === "msg");
|
|
297
|
+
const msgArgLength = msgArgs.length;
|
|
298
|
+
const serializerMap = this.#makeArgSerializer(endpoint.args);
|
|
299
|
+
return async (...argData: unknown[]) => {
|
|
300
|
+
const args = argData.slice(0, msgArgLength);
|
|
301
|
+
const data = msgArgs.map((arg, idx) => serializerMap.get(arg.name)?.(args[idx]) ?? null);
|
|
302
|
+
this.ws.emit(key, data);
|
|
225
303
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
304
|
+
});
|
|
305
|
+
this.#handlerFactory.set(`listen${capitalize(key)}`, () => {
|
|
306
|
+
const parseReturn = this.#makeReturnParser(endpoint.returns);
|
|
307
|
+
const wrappedListeners = new WeakMap<(data: unknown) => void, (data: unknown) => void>();
|
|
308
|
+
return ((handleEvent: (data: unknown) => void, fetchPolicy: FetchPolicy = {}) => {
|
|
309
|
+
const wrapped = (data: unknown) => {
|
|
310
|
+
const parsedReturn = parseReturn(data, { crystalize: fetchPolicy?.crystalize ?? true });
|
|
311
|
+
handleEvent(parsedReturn);
|
|
312
|
+
};
|
|
313
|
+
wrappedListeners.set(handleEvent, wrapped);
|
|
314
|
+
this.ws.on(key, wrapped);
|
|
315
|
+
return () => this.ws.off(key, wrappedListeners.get(handleEvent) ?? handleEvent);
|
|
316
|
+
}) as FetchHandler;
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
231
319
|
}
|
|
232
320
|
default:
|
|
233
321
|
this.logger.error(`Unsupported endpoint type: ${endpoint.type}`);
|
|
@@ -302,7 +390,6 @@ export class FetchClient {
|
|
|
302
390
|
return endpoint;
|
|
303
391
|
}
|
|
304
392
|
#registerModelBaseEndpoint(refName: string, signal: SerializedSignal) {
|
|
305
|
-
const cnst = ConstantRegistry.getDatabase(refName);
|
|
306
393
|
const capRefName = capitalize(refName);
|
|
307
394
|
const names = {
|
|
308
395
|
createModel: `create${capRefName}`,
|
|
@@ -319,66 +406,90 @@ export class FetchClient {
|
|
|
319
406
|
mergeModel: `merge${capRefName}`,
|
|
320
407
|
};
|
|
321
408
|
const endpoint = FetchClient.getBaseEndpoint(refName, signal);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
325
|
-
Object.assign(this.handler, handler);
|
|
326
|
-
|
|
327
|
-
if (signal.cruGuards)
|
|
328
|
-
Object.assign(this.handler, {
|
|
329
|
-
[names.viewModel]: async (id: string, option?: FetchPolicy) => {
|
|
330
|
-
const modelObj = await this.handler[names.model](id, { ...option, crystalize: false });
|
|
331
|
-
const model = new cnst.full(modelObj as object);
|
|
332
|
-
return {
|
|
333
|
-
[refName]: model,
|
|
334
|
-
[`${refName}View`]: { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() },
|
|
335
|
-
};
|
|
336
|
-
},
|
|
337
|
-
[names.getModelView]: async (id: string, option?: FetchPolicy) => {
|
|
338
|
-
const modelObj = await this.handler[names.model](id, { ...option, crystalize: false });
|
|
339
|
-
return { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() };
|
|
340
|
-
},
|
|
341
|
-
[names.editModel]: async (id: string, option?: FetchPolicy) => {
|
|
342
|
-
const modelObj = await this.handler[names.model](id, { ...option, crystalize: false });
|
|
343
|
-
const model = new cnst.full(modelObj as object);
|
|
344
|
-
return {
|
|
345
|
-
[refName]: model,
|
|
346
|
-
[`${refName}Edit`]: { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() },
|
|
347
|
-
};
|
|
348
|
-
},
|
|
349
|
-
[names.getModelEdit]: async (id: string, option?: FetchPolicy) => {
|
|
350
|
-
const modelObj = await this.handler[names.model](id, { ...option, crystalize: false });
|
|
351
|
-
return { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() };
|
|
352
|
-
},
|
|
353
|
-
});
|
|
409
|
+
Object.entries(endpoint).forEach(([key, value]) => {
|
|
410
|
+
this.#handlerFactory.set(key, () => this.#makeHttpFn(key, value, signal.prefix));
|
|
411
|
+
});
|
|
354
412
|
|
|
355
|
-
if (signal.cruGuards)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
413
|
+
if (signal.cruGuards) {
|
|
414
|
+
this.#handlerFactory.set(
|
|
415
|
+
names.viewModel,
|
|
416
|
+
() =>
|
|
417
|
+
(async (id: string, option?: FetchPolicy) => {
|
|
418
|
+
const cnst = ConstantRegistry.getDatabase(refName);
|
|
419
|
+
const modelFn = this.#requireHandler(names.model, names.viewModel);
|
|
420
|
+
const modelObj = await modelFn(id, { ...option, crystalize: false });
|
|
421
|
+
const model = new cnst.full(modelObj as object);
|
|
422
|
+
return {
|
|
423
|
+
[refName]: model,
|
|
424
|
+
[`${refName}View`]: { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() },
|
|
425
|
+
};
|
|
426
|
+
}) as FetchHandler,
|
|
427
|
+
);
|
|
428
|
+
this.#handlerFactory.set(
|
|
429
|
+
names.getModelView,
|
|
430
|
+
() =>
|
|
431
|
+
(async (id: string, option?: FetchPolicy) => {
|
|
432
|
+
const modelFn = this.#requireHandler(names.model, names.getModelView);
|
|
433
|
+
const modelObj = await modelFn(id, { ...option, crystalize: false });
|
|
434
|
+
return { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() };
|
|
435
|
+
}) as FetchHandler,
|
|
436
|
+
);
|
|
437
|
+
this.#handlerFactory.set(
|
|
438
|
+
names.editModel,
|
|
439
|
+
() =>
|
|
440
|
+
(async (id: string, option?: FetchPolicy) => {
|
|
441
|
+
const cnst = ConstantRegistry.getDatabase(refName);
|
|
442
|
+
const modelFn = this.#requireHandler(names.model, names.editModel);
|
|
443
|
+
const modelObj = await modelFn(id, { ...option, crystalize: false });
|
|
444
|
+
const model = new cnst.full(modelObj as object);
|
|
445
|
+
return {
|
|
446
|
+
[refName]: model,
|
|
447
|
+
[`${refName}Edit`]: { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() },
|
|
448
|
+
};
|
|
449
|
+
}) as FetchHandler,
|
|
450
|
+
);
|
|
451
|
+
this.#handlerFactory.set(
|
|
452
|
+
names.getModelEdit,
|
|
453
|
+
() =>
|
|
454
|
+
(async (id: string, option?: FetchPolicy) => {
|
|
455
|
+
const modelFn = this.#requireHandler(names.model, names.getModelEdit);
|
|
456
|
+
const modelObj = await modelFn(id, { ...option, crystalize: false });
|
|
457
|
+
return { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() };
|
|
458
|
+
}) as FetchHandler,
|
|
459
|
+
);
|
|
460
|
+
this.#handlerFactory.set(
|
|
461
|
+
names.mergeModel,
|
|
462
|
+
() =>
|
|
463
|
+
(async (modelOrId: string | { id: string }, data: UnknownRecord, option?: FetchPolicy) => {
|
|
464
|
+
const id = typeof modelOrId === "string" ? modelOrId : modelOrId.id;
|
|
465
|
+
const updateFn = this.#requireHandler(names.updateModel, names.mergeModel);
|
|
466
|
+
return await updateFn(id, data, option);
|
|
467
|
+
}) as FetchHandler,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
362
470
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
471
|
+
this.#handlerFactory.set(
|
|
472
|
+
names.addModelFiles,
|
|
473
|
+
() =>
|
|
474
|
+
(async (fileList: FileList, parentId?: string, option?: FetchPolicy) => {
|
|
475
|
+
const cap = resolveFileUploadCapability(this.serializedSignal);
|
|
476
|
+
const endpoint = cap ? this.serializedSignal[cap.refName]?.endpoint[cap.endpointKey] : undefined;
|
|
477
|
+
if (!cap || !endpoint)
|
|
478
|
+
throw new Error(
|
|
479
|
+
"File upload is not configured. Mark an upload mutation with { fileUpload: true } (e.g. shared FileEndpoint.addFiles).",
|
|
480
|
+
);
|
|
481
|
+
const { fields, buildMetas } = fileUploadContract;
|
|
482
|
+
const formData = new FormData();
|
|
483
|
+
for (let i = 0; i < fileList.length; i++) formData.append(fields.files, fileList[i]);
|
|
484
|
+
formData.append(fields.metas, JSON.stringify(buildMetas(fileList)));
|
|
485
|
+
formData.append(fields.type, refName);
|
|
486
|
+
if (parentId) formData.append(fields.parentId, parentId);
|
|
487
|
+
const url = FetchClient.makeHttpUrl(cap.endpointKey, endpoint, cap.prefix, new Map());
|
|
488
|
+
return await this.http.post(url, formData, { headers: this.#makeAuthHeaders(option) });
|
|
489
|
+
}) as FetchHandler,
|
|
490
|
+
);
|
|
381
491
|
}
|
|
492
|
+
|
|
382
493
|
static getEndpointFromSlice(
|
|
383
494
|
refName: string,
|
|
384
495
|
suffix: string,
|
|
@@ -406,7 +517,6 @@ export class FetchClient {
|
|
|
406
517
|
return endpoint;
|
|
407
518
|
}
|
|
408
519
|
#registerSlice(refName: string, suffix: string, slice: SerializedSlice, prefix?: string) {
|
|
409
|
-
const cnst = ConstantRegistry.getDatabase(refName);
|
|
410
520
|
const capSuffix = capitalize(suffix);
|
|
411
521
|
const sliceName = `${refName}${capSuffix}`;
|
|
412
522
|
const capRefName = capitalize(refName);
|
|
@@ -418,16 +528,14 @@ export class FetchClient {
|
|
|
418
528
|
};
|
|
419
529
|
|
|
420
530
|
const endpoint = FetchClient.getEndpointFromSlice(refName, suffix, slice);
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
);
|
|
424
|
-
Object.assign(this.handler, handler);
|
|
531
|
+
Object.entries(endpoint).forEach(([key, value]) => {
|
|
532
|
+
this.#handlerFactory.set(key, () => this.#makeHttpFn(key, value, prefix));
|
|
533
|
+
});
|
|
425
534
|
|
|
426
535
|
const argLength = slice.args.length;
|
|
427
536
|
this.slice[sliceName] = { refName, sliceName, argLength };
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const initFn = async (...argData: unknown[]) => {
|
|
537
|
+
this.#handlerFactory.set(names.init, () => async (...argData: unknown[]) => {
|
|
538
|
+
const cnst = ConstantRegistry.getDatabase(refName);
|
|
431
539
|
const queryArgs = normalizeQueryArgs(
|
|
432
540
|
Array.from({ length: Math.min(argData.length, argLength) }, (_, idx) => argData[idx]),
|
|
433
541
|
slice.args,
|
|
@@ -436,6 +544,8 @@ export class FetchClient {
|
|
|
436
544
|
const option = (argData[argLength] ?? {}) as { page?: number; limit?: number; sort?: string; insight?: boolean };
|
|
437
545
|
const { page = 1, limit = 20, sort = "latest", insight: fetchInsight = true } = option;
|
|
438
546
|
const skip = (page - 1) * limit;
|
|
547
|
+
const listFn = this.#requireHandler<(...args: unknown[]) => Promise<unknown[]>>(names.list, names.init);
|
|
548
|
+
const insightFn = this.#requireHandler<(...args: unknown[]) => Promise<unknown>>(names.insight, names.init);
|
|
439
549
|
|
|
440
550
|
const [modelObjList, modelObjInsight] = (await Promise.all([
|
|
441
551
|
listFn(...fetchQueryArgs, skip, limit, sort, { ...option, crystalize: false }),
|
|
@@ -465,13 +575,14 @@ export class FetchClient {
|
|
|
465
575
|
[`${refName}List${capSuffix}`]: modelList,
|
|
466
576
|
[`${refName}Insight${capSuffix}`]: modelInsight,
|
|
467
577
|
};
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
[
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
578
|
+
});
|
|
579
|
+
this.#handlerFactory.set(names.getInit, () => async (...args: unknown[]) => {
|
|
580
|
+
const initFn = this.#requireHandler<(...args: unknown[]) => Promise<Record<string, unknown>>>(
|
|
581
|
+
names.init,
|
|
582
|
+
names.getInit,
|
|
583
|
+
);
|
|
584
|
+
const result = await initFn(...args);
|
|
585
|
+
return result[`${refName}Init${capSuffix}`];
|
|
475
586
|
});
|
|
476
587
|
}
|
|
477
588
|
#makeArgSerializer(args: SerializedArg[]) {
|
|
@@ -540,34 +651,18 @@ export class FetchClient {
|
|
|
540
651
|
>(...signals: Signals): FetchProxy<MergeAllFetchTypes<Signals>, GetSliceMetaObjFromDatabaseSignals<Signals>> {
|
|
541
652
|
const serializedSignal: { [key: string]: SerializedSignal } = {};
|
|
542
653
|
const handler: Record<string, FetchHandler> = {};
|
|
543
|
-
const mergeSerializedSignal = (refName: string, signal: SerializedSignal) => {
|
|
544
|
-
const current = serializedSignal[refName];
|
|
545
|
-
serializedSignal[refName] = current
|
|
546
|
-
? {
|
|
547
|
-
...current,
|
|
548
|
-
...signal,
|
|
549
|
-
endpoint: { ...current.endpoint, ...signal.endpoint },
|
|
550
|
-
slice: current.slice || signal.slice ? { ...current.slice, ...signal.slice } : undefined,
|
|
551
|
-
filter:
|
|
552
|
-
current.filter || signal.filter
|
|
553
|
-
? {
|
|
554
|
-
filter: { ...current.filter?.filter, ...signal.filter?.filter },
|
|
555
|
-
sortKeys: [...new Set([...(current.filter?.sortKeys ?? []), ...(signal.filter?.sortKeys ?? [])])],
|
|
556
|
-
}
|
|
557
|
-
: undefined,
|
|
558
|
-
getGuards: signal.getGuards ?? current.getGuards,
|
|
559
|
-
cruGuards: signal.cruGuards ?? current.cruGuards,
|
|
560
|
-
}
|
|
561
|
-
: signal;
|
|
562
|
-
};
|
|
563
654
|
signals.forEach((signal) => {
|
|
564
655
|
if ("endpoint" in signal) {
|
|
565
656
|
const refName = (signal as DatabaseSignal | ServiceSignal).endpoint.baseName;
|
|
566
|
-
|
|
657
|
+
FetchClient.#mergeSerializedSignalInto(
|
|
658
|
+
serializedSignal,
|
|
659
|
+
refName,
|
|
660
|
+
(signal as DatabaseSignal | ServiceSignal).serializedSignal,
|
|
661
|
+
);
|
|
567
662
|
} else {
|
|
568
663
|
Object.assign(handler, (signal as FetchClient).handler);
|
|
569
664
|
Object.entries((signal as FetchClient).serializedSignal).forEach(([refName, signal]) => {
|
|
570
|
-
|
|
665
|
+
FetchClient.#mergeSerializedSignalInto(serializedSignal, refName, signal);
|
|
571
666
|
});
|
|
572
667
|
}
|
|
573
668
|
});
|
|
@@ -613,8 +708,11 @@ export class FetchClient {
|
|
|
613
708
|
get(target, prop) {
|
|
614
709
|
if (prop in target) return (target as any)[prop];
|
|
615
710
|
else if (prop === "instance") return instance;
|
|
616
|
-
else if (prop
|
|
617
|
-
|
|
711
|
+
else if (typeof prop === "string") {
|
|
712
|
+
const handler = instance.#getOrCreateHandler(prop);
|
|
713
|
+
if (handler) return handler;
|
|
714
|
+
}
|
|
715
|
+
return (instance as unknown as Record<PropertyKey, unknown>)[prop];
|
|
618
716
|
},
|
|
619
717
|
}) as FetchProxy<FetchType, SliceMetaObj>;
|
|
620
718
|
}
|
package/package.json
CHANGED
|
@@ -34,9 +34,12 @@ export declare class FetchClient {
|
|
|
34
34
|
constructor(origin: string, handler?: Record<string, FetchHandler>, serializedSignal?: {
|
|
35
35
|
[key: string]: SerializedSignal;
|
|
36
36
|
}, ErrorCls?: ErrorConstructor | undefined);
|
|
37
|
+
static resetSharedRegistry(): void;
|
|
37
38
|
setErrorConstructor(ErrorCls?: ErrorConstructor): void;
|
|
38
39
|
applySignal(serializedSignal: {
|
|
39
40
|
[key: string]: SerializedSignal;
|
|
41
|
+
}, { share }?: {
|
|
42
|
+
share?: boolean;
|
|
40
43
|
}): this;
|
|
41
44
|
connect(): void;
|
|
42
45
|
disconnect(): void;
|