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.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}`);
@@ -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
- 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
- });
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
- 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
- });
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
- 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
- });
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
- 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[]) => {
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
- 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
- },
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
- mergeSerializedSignal(refName, (signal as DatabaseSignal | ServiceSignal).serializedSignal);
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
- mergeSerializedSignal(refName, signal);
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 in instance.handler) return instance.handler[prop as string];
617
- else return (instance as any)[prop as string];
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "2.2.6",
3
+ "version": "2.2.7-rc.1",
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;