akanjs 2.2.5 → 2.2.7-rc.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.
@@ -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.handler = handler;
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.assign(this.serializedSignal, serializedSignal);
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
- return Object.assign(this.handler, { [key]: this.#makeHttpFn(key, endpoint, prefix) });
259
+ this.#handlerFactory.set(key, () => this.#makeHttpFn(key, endpoint, prefix));
260
+ return;
180
261
  }
181
262
  case "mutation": {
182
- return Object.assign(this.handler, { [key]: this.#makeHttpFn(key, endpoint, prefix) });
263
+ this.#handlerFactory.set(key, () => this.#makeHttpFn(key, endpoint, prefix));
264
+ return;
183
265
  }
184
266
  case "pubsub": {
185
- const roomArgs = endpoint.args.filter((arg) => arg.type === "room");
186
- const roomArgLength = roomArgs.length;
187
- const serializerMap = this.#makeArgSerializer(endpoint.args);
188
- const parseReturn = this.#makeReturnParser(endpoint.returns);
189
- const wrappedListeners = new WeakMap<(data: unknown) => void, (data: unknown) => void>();
190
- const pubsubFn = async (...argData: unknown[]) => {
191
- const args = argData.slice(0, roomArgLength);
192
- const handleEvent = argData[roomArgLength] as (data: unknown) => void;
193
- const fetchPolicy = argData[roomArgLength + 1] as FetchPolicy | undefined;
194
- const data = roomArgs.map((arg, idx) => serializerMap.get(arg.name)?.(args[idx]) ?? null);
195
- const wrapped = (data: unknown) => {
196
- const parsedReturn = parseReturn(data, { crystalize: fetchPolicy?.crystalize ?? true });
197
- handleEvent(parsedReturn);
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
- wrappedListeners.set(handleEvent, wrapped);
200
- this.ws.subscribe({
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
- const msgArgs = endpoint.args.filter((arg) => arg.type === "msg");
212
- const msgArgLength = msgArgs.length;
213
- const serializerMap = this.#makeArgSerializer(endpoint.args);
214
- const parseReturn = this.#makeReturnParser(endpoint.returns);
215
- const wrappedListeners = new WeakMap<(data: unknown) => void, (data: unknown) => void>();
216
- const messageFn = async (...argData: unknown[]) => {
217
- const args = argData.slice(0, msgArgLength);
218
- const data = msgArgs.map((arg, idx) => serializerMap.get(arg.name)?.(args[idx]) ?? null);
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
- wrappedListeners.set(handleEvent, wrapped);
227
- this.ws.on(key, wrapped);
228
- return () => this.ws.off(key, wrappedListeners.get(handleEvent) ?? handleEvent);
229
- };
230
- return Object.assign(this.handler, { [key]: messageFn, [`listen${capitalize(key)}`]: listenFn });
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}`);
@@ -319,66 +407,88 @@ export class FetchClient {
319
407
  mergeModel: `merge${capRefName}`,
320
408
  };
321
409
  const endpoint = FetchClient.getBaseEndpoint(refName, signal);
322
- const handler = Object.fromEntries(
323
- Object.entries(endpoint).map(([key, value]) => [key, this.#makeHttpFn(key, value, signal.prefix)]),
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
- });
410
+ Object.entries(endpoint).forEach(([key, value]) => {
411
+ this.#handlerFactory.set(key, () => this.#makeHttpFn(key, value, signal.prefix));
412
+ });
354
413
 
355
- if (signal.cruGuards)
356
- Object.assign(this.handler, {
357
- [names.mergeModel]: async (modelOrId: string | { id: string }, data: UnknownRecord, option?: FetchPolicy) => {
358
- const id = typeof modelOrId === "string" ? modelOrId : modelOrId.id;
359
- return await this.handler[names.updateModel](id, data, option);
360
- },
361
- });
414
+ if (signal.cruGuards) {
415
+ this.#handlerFactory.set(
416
+ names.viewModel,
417
+ () =>
418
+ (async (id: string, option?: FetchPolicy) => {
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 modelFn = this.#requireHandler(names.model, names.editModel);
442
+ const modelObj = await modelFn(id, { ...option, crystalize: false });
443
+ const model = new cnst.full(modelObj as object);
444
+ return {
445
+ [refName]: model,
446
+ [`${refName}Edit`]: { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() },
447
+ };
448
+ }) as FetchHandler,
449
+ );
450
+ this.#handlerFactory.set(
451
+ names.getModelEdit,
452
+ () =>
453
+ (async (id: string, option?: FetchPolicy) => {
454
+ const modelFn = this.#requireHandler(names.model, names.getModelEdit);
455
+ const modelObj = await modelFn(id, { ...option, crystalize: false });
456
+ return { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() };
457
+ }) as FetchHandler,
458
+ );
459
+ this.#handlerFactory.set(
460
+ names.mergeModel,
461
+ () =>
462
+ (async (modelOrId: string | { id: string }, data: UnknownRecord, option?: FetchPolicy) => {
463
+ const id = typeof modelOrId === "string" ? modelOrId : modelOrId.id;
464
+ const updateFn = this.#requireHandler(names.updateModel, names.mergeModel);
465
+ return await updateFn(id, data, option);
466
+ }) as FetchHandler,
467
+ );
468
+ }
362
469
 
363
- Object.assign(this.handler, {
364
- [names.addModelFiles]: async (fileList: FileList, parentId?: string, option?: FetchPolicy) => {
365
- const cap = resolveFileUploadCapability(this.serializedSignal);
366
- const endpoint = cap ? this.serializedSignal[cap.refName]?.endpoint[cap.endpointKey] : undefined;
367
- if (!cap || !endpoint)
368
- throw new Error(
369
- "File upload is not configured. Mark an upload mutation with { fileUpload: true } (e.g. shared FileEndpoint.addFiles).",
370
- );
371
- const { fields, buildMetas } = fileUploadContract;
372
- const formData = new FormData();
373
- for (let i = 0; i < fileList.length; i++) formData.append(fields.files, fileList[i]);
374
- formData.append(fields.metas, JSON.stringify(buildMetas(fileList)));
375
- formData.append(fields.type, refName);
376
- if (parentId) formData.append(fields.parentId, parentId);
377
- const url = FetchClient.makeHttpUrl(cap.endpointKey, endpoint, cap.prefix, new Map());
378
- return await this.http.post(url, formData, { headers: this.#makeAuthHeaders(option) });
379
- },
380
- });
470
+ this.#handlerFactory.set(
471
+ names.addModelFiles,
472
+ () =>
473
+ (async (fileList: FileList, parentId?: string, option?: FetchPolicy) => {
474
+ const cap = resolveFileUploadCapability(this.serializedSignal);
475
+ const endpoint = cap ? this.serializedSignal[cap.refName]?.endpoint[cap.endpointKey] : undefined;
476
+ if (!cap || !endpoint)
477
+ throw new Error(
478
+ "File upload is not configured. Mark an upload mutation with { fileUpload: true } (e.g. shared FileEndpoint.addFiles).",
479
+ );
480
+ const { fields, buildMetas } = fileUploadContract;
481
+ const formData = new FormData();
482
+ for (let i = 0; i < fileList.length; i++) formData.append(fields.files, fileList[i]);
483
+ formData.append(fields.metas, JSON.stringify(buildMetas(fileList)));
484
+ formData.append(fields.type, refName);
485
+ if (parentId) formData.append(fields.parentId, parentId);
486
+ const url = FetchClient.makeHttpUrl(cap.endpointKey, endpoint, cap.prefix, new Map());
487
+ return await this.http.post(url, formData, { headers: this.#makeAuthHeaders(option) });
488
+ }) as FetchHandler,
489
+ );
381
490
  }
491
+
382
492
  static getEndpointFromSlice(
383
493
  refName: string,
384
494
  suffix: string,
@@ -418,16 +528,13 @@ export class FetchClient {
418
528
  };
419
529
 
420
530
  const endpoint = FetchClient.getEndpointFromSlice(refName, suffix, slice);
421
- const handler = Object.fromEntries(
422
- Object.entries(endpoint).map(([key, value]) => [key, this.#makeHttpFn(key, value, prefix)]),
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
- const listFn = this.handler[names.list] as (...args: unknown[]) => Promise<unknown[]>;
429
- const insightFn = this.handler[names.insight] as (...args: unknown[]) => Promise<unknown>;
430
- const initFn = async (...argData: unknown[]) => {
537
+ this.#handlerFactory.set(names.init, () => async (...argData: unknown[]) => {
431
538
  const queryArgs = normalizeQueryArgs(
432
539
  Array.from({ length: Math.min(argData.length, argLength) }, (_, idx) => argData[idx]),
433
540
  slice.args,
@@ -436,6 +543,8 @@ export class FetchClient {
436
543
  const option = (argData[argLength] ?? {}) as { page?: number; limit?: number; sort?: string; insight?: boolean };
437
544
  const { page = 1, limit = 20, sort = "latest", insight: fetchInsight = true } = option;
438
545
  const skip = (page - 1) * limit;
546
+ const listFn = this.#requireHandler<(...args: unknown[]) => Promise<unknown[]>>(names.list, names.init);
547
+ const insightFn = this.#requireHandler<(...args: unknown[]) => Promise<unknown>>(names.insight, names.init);
439
548
 
440
549
  const [modelObjList, modelObjInsight] = (await Promise.all([
441
550
  listFn(...fetchQueryArgs, skip, limit, sort, { ...option, crystalize: false }),
@@ -465,13 +574,14 @@ export class FetchClient {
465
574
  [`${refName}List${capSuffix}`]: modelList,
466
575
  [`${refName}Insight${capSuffix}`]: modelInsight,
467
576
  };
468
- };
469
- Object.assign(this.handler, {
470
- [names.init]: initFn,
471
- [names.getInit]: async (...args: unknown[]) => {
472
- const result = await initFn(...args);
473
- return result[`${refName}Init${capSuffix}`];
474
- },
577
+ });
578
+ this.#handlerFactory.set(names.getInit, () => async (...args: unknown[]) => {
579
+ const initFn = this.#requireHandler<(...args: unknown[]) => Promise<Record<string, unknown>>>(
580
+ names.init,
581
+ names.getInit,
582
+ );
583
+ const result = await initFn(...args);
584
+ return result[`${refName}Init${capSuffix}`];
475
585
  });
476
586
  }
477
587
  #makeArgSerializer(args: SerializedArg[]) {
@@ -540,34 +650,18 @@ export class FetchClient {
540
650
  >(...signals: Signals): FetchProxy<MergeAllFetchTypes<Signals>, GetSliceMetaObjFromDatabaseSignals<Signals>> {
541
651
  const serializedSignal: { [key: string]: SerializedSignal } = {};
542
652
  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
653
  signals.forEach((signal) => {
564
654
  if ("endpoint" in signal) {
565
655
  const refName = (signal as DatabaseSignal | ServiceSignal).endpoint.baseName;
566
- mergeSerializedSignal(refName, (signal as DatabaseSignal | ServiceSignal).serializedSignal);
656
+ FetchClient.#mergeSerializedSignalInto(
657
+ serializedSignal,
658
+ refName,
659
+ (signal as DatabaseSignal | ServiceSignal).serializedSignal,
660
+ );
567
661
  } else {
568
662
  Object.assign(handler, (signal as FetchClient).handler);
569
663
  Object.entries((signal as FetchClient).serializedSignal).forEach(([refName, signal]) => {
570
- mergeSerializedSignal(refName, signal);
664
+ FetchClient.#mergeSerializedSignalInto(serializedSignal, refName, signal);
571
665
  });
572
666
  }
573
667
  });
@@ -613,8 +707,11 @@ export class FetchClient {
613
707
  get(target, prop) {
614
708
  if (prop in target) return (target as any)[prop];
615
709
  else if (prop === "instance") return instance;
616
- else if (prop in instance.handler) return instance.handler[prop as string];
617
- else return (instance as any)[prop as string];
710
+ else if (typeof prop === "string") {
711
+ const handler = instance.#getOrCreateHandler(prop);
712
+ if (handler) return handler;
713
+ }
714
+ return (instance as unknown as Record<PropertyKey, unknown>)[prop];
618
715
  },
619
716
  }) as FetchProxy<FetchType, SliceMetaObj>;
620
717
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "2.2.5",
3
+ "version": "2.2.7-rc.0",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -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;