akanjs 3.0.0-alpha.67 → 3.0.0-alpha.69

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.
Files changed (68) hide show
  1. package/common/mcpExposure.ts +14 -1
  2. package/constant/crystalize.ts +45 -1
  3. package/constant/deserialize.ts +3 -2
  4. package/constant/types.ts +5 -1
  5. package/dictionary/base.dictionary.ts +18 -0
  6. package/fetch/client/fetchClient.ts +23 -2
  7. package/fetch/client/httpClient.ts +77 -9
  8. package/fetch/fetchType/appliedReturn.type.ts +48 -44
  9. package/fetch/fetchType/endpointFetch.type.ts +6 -3
  10. package/fetch/fetchType/sliceFetch.type.ts +27 -50
  11. package/index.ts +25 -0
  12. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  13. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  14. package/package.json +2 -2
  15. package/server/akanApp.ts +93 -24
  16. package/server/akanAppHeaders.ts +2 -0
  17. package/server/assetEncoding.ts +4 -24
  18. package/server/console.ts +9 -101
  19. package/server/consoleEvaluator.ts +51 -0
  20. package/server/consolePasteFilter.ts +91 -0
  21. package/server/consoleSession.ts +198 -0
  22. package/server/contentEncoding.ts +83 -0
  23. package/server/imageOptimizer.ts +122 -12
  24. package/server/mcp/McpRouter.ts +28 -2
  25. package/server/routing/apiRouter.ts +35 -11
  26. package/server/semaphore.ts +22 -0
  27. package/server/types.tsx +3 -0
  28. package/signal/mcp/McpDocument.ts +43 -3
  29. package/signal/mcp/McpUriTemplate.ts +1 -6
  30. package/signal/serializer/fetch.serializer.ts +14 -0
  31. package/signal/signalContext.ts +164 -33
  32. package/signal/slice.ts +32 -1
  33. package/signal/types.ts +34 -3
  34. package/store/action.ts +13 -13
  35. package/types/common/mcpExposure.d.ts +5 -1
  36. package/types/constant/constantRegistry.d.ts +2 -10
  37. package/types/constant/crystalize.d.ts +2 -0
  38. package/types/constant/types.d.ts +3 -3
  39. package/types/dictionary/base.dictionary.d.ts +1 -1
  40. package/types/dictionary/dictionary.d.ts +3 -3
  41. package/types/fetch/agentTurn.d.ts +1 -119
  42. package/types/fetch/fetchType/appliedReturn.type.d.ts +21 -18
  43. package/types/fetch/fetchType/endpointFetch.type.d.ts +2 -1
  44. package/types/fetch/fetchType/sliceFetch.type.d.ts +11 -15
  45. package/types/index.d.ts +23 -0
  46. package/types/server/assetEncoding.d.ts +1 -1
  47. package/types/server/console.d.ts +1 -1
  48. package/types/server/consoleEvaluator.d.ts +2 -0
  49. package/types/server/consolePasteFilter.d.ts +11 -0
  50. package/types/server/consoleSession.d.ts +12 -0
  51. package/types/server/contentEncoding.d.ts +28 -0
  52. package/types/server/mcp/McpRouter.d.ts +2 -0
  53. package/types/server/semaphore.d.ts +5 -0
  54. package/types/server/types.d.ts +2 -0
  55. package/types/signal/mcp/McpDocument.d.ts +19 -0
  56. package/types/signal/mcp/McpUriTemplate.d.ts +0 -1
  57. package/types/signal/signalContext.d.ts +8 -1
  58. package/types/signal/slice.d.ts +25 -0
  59. package/types/signal/types.d.ts +33 -3
  60. package/types/store/action.d.ts +7 -5
  61. package/types/store/baseSt.d.ts +2 -6
  62. package/types/ui/System/Client.d.ts +1 -2
  63. package/types/ui/System/Common.d.ts +3 -4
  64. package/ui/Load/Units.tsx +4 -2
  65. package/ui/Signal/Doc.tsx +1 -1
  66. package/ui/Signal/RestApi.tsx +1 -1
  67. package/ui/System/Client.tsx +6 -2
  68. package/ui/System/Common.tsx +3 -4
@@ -16,9 +16,13 @@ export interface McpExposureEndpoint {
16
16
  args: { name: string; refName: string; type: string; arrDepth?: number; nullable?: boolean }[];
17
17
  guards?: string[];
18
18
  fileUpload?: boolean;
19
+ mcp?: boolean;
19
20
  }
20
21
 
21
22
  export interface McpExposureOption {
23
+ /** The model the endpoint belongs to and the name it is published under: one rule reads the key, not the shape. */
24
+ refName: string;
25
+ key: string;
22
26
  /**
23
27
  * The read-only deployment valve, which is server configuration. The browser explorer cannot know it and so
24
28
  * badges what the code decided; the boot log is where a read-only deployment says what it dropped.
@@ -56,10 +60,19 @@ export const mcpHintsOf = (key: string, endpoint: { type: string }) => {
56
60
  };
57
61
 
58
62
  /** The sentence explaining why this endpoint is not in the catalogue, or `null` when it is. */
59
- export const mcpRefusalOf = (endpoint: McpExposureEndpoint, { readOnly }: McpExposureOption = {}): string | null => {
63
+ export const mcpRefusalOf = (
64
+ endpoint: McpExposureEndpoint,
65
+ { refName, key, readOnly }: McpExposureOption,
66
+ ): string | null => {
67
+
68
+ if (endpoint.mcp === false)
69
+ return "it declares `mcp: false`, so it is deliberately off the agent shelf. HTTP still serves it.";
60
70
 
61
71
  if (!endpoint.guards?.length)
62
72
  return "it declares no guards, and exposure follows them — write `guards: [Public]` if anonymous access is the intent.";
73
+
74
+ if (key === `light${capitalize(refName)}`)
75
+ return `it reads the same document as \`${refName}\` in a smaller shape — call \`${refName}\` instead.`;
63
76
  if (endpoint.type === "prompt") return mcpPromptRefusalOf(endpoint);
64
77
  if (endpoint.type === "pubsub" || endpoint.type === "message")
65
78
  return `\`${endpoint.type}\` rides the websocket, and its internal arguments read a socket an MCP request does not have.`;
@@ -11,6 +11,50 @@ import type { FieldProps } from ".";
11
11
 
12
12
  export type CrystalizeFunc<Model> = (self: GetStateObject<Model>, isChild?: boolean) => Model;
13
13
 
14
+ /**
15
+ * The relation instances one hydration pass has already built, by model class and then document id.
16
+ *
17
+ * A listing hands the same relation to every row that references it — twenty users wearing one avatar — and
18
+ * each row would otherwise build its own copy of it, re-parsing every `Date` on the way. Sharing is safe
19
+ * because a constant model is `[immerable]`: a store write copies before it mutates.
20
+ *
21
+ * Module-scoped rather than passed down, because `crystalize` is reached through a model's own constructor,
22
+ * which has nowhere to carry it. A pass is wholly synchronous, so no other one can observe this mid-flight.
23
+ */
24
+ let sharedInstances: Map<Cls, Map<string, object>> | null = null;
25
+
26
+ /** Runs one hydration pass, sharing a relation instance across every value in it that names the same id. */
27
+ export const withSharedInstances = <T>(hydrate: () => T): T => {
28
+ if (sharedInstances) return hydrate();
29
+ sharedInstances = new Map();
30
+ try {
31
+ return hydrate();
32
+ } finally {
33
+ sharedInstances = null;
34
+ }
35
+ };
36
+
37
+ type ModelCls = Cls<{ set: (obj: object) => object }>;
38
+
39
+ const relationIdOf = (value: object): string | null => {
40
+ const id = (value as { id?: unknown }).id;
41
+ return typeof id === "string" && id ? id : null;
42
+ };
43
+
44
+ const crystalizeModel = (field: FieldProps, value: object): object => {
45
+ const modelRef = field.modelRef as ModelCls;
46
+
47
+ if (value instanceof modelRef) return value;
48
+
49
+ const id = field.isScalar ? null : relationIdOf(value);
50
+ if (!sharedInstances || !id) return new modelRef().set(value);
51
+ const byId = sharedInstances.get(modelRef) ?? new Map<string, object>();
52
+ if (!sharedInstances.has(modelRef)) sharedInstances.set(modelRef, byId);
53
+ const shared = byId.get(id) ?? new modelRef().set(value);
54
+ byId.set(id, shared);
55
+ return shared;
56
+ };
57
+
14
58
  export const crystalize = (field: FieldProps, value: unknown): unknown => {
15
59
  if (value === undefined || value === null) return value as undefined | null;
16
60
  if (field.isArray && Array.isArray(value))
@@ -41,7 +85,7 @@ export const crystalize = (field: FieldProps, value: unknown): unknown => {
41
85
  ]),
42
86
  );
43
87
  }
44
- if (field.isClass) return new (field.modelRef as Cls<{ set: (obj: object) => object }>)().set(value as object);
88
+ if (field.isClass) return crystalizeModel(field, value as object);
45
89
  if (field.modelRef === Date) return dayjs(value as Date);
46
90
  return crystalizeValue(value);
47
91
  };
@@ -1,6 +1,6 @@
1
1
  import { Any, applyFnToArrayObjects, type Cls, FIELD_META, PrimitiveRegistry, type PrimitiveScalar } from "akanjs/base";
2
2
 
3
- import { type ConstantCls, type ConstantModelRef, ConstantRegistry, type FieldProps } from ".";
3
+ import { type ConstantCls, type ConstantModelRef, ConstantRegistry, type FieldProps, withSharedInstances } from ".";
4
4
 
5
5
  const getDeserializeFn = (inputRef: ConstantModelRef | PrimitiveScalar) => {
6
6
  const deserializeFn = PrimitiveRegistry.has(inputRef as Cls)
@@ -66,5 +66,6 @@ export const deserialize = (
66
66
  if (nullable && (value === null || value === undefined)) return null;
67
67
  else if (!nullable && (value === null || value === undefined) && argRef !== Any)
68
68
  throw new Error(`Invalid Value (Nullable) in ${key} ${argRef} for value ${value}`);
69
- return deserializeInput(value, argRef, arrDepth, convertFn) as object[];
69
+
70
+ return withSharedInstances(() => deserializeInput(value, argRef, arrDepth, convertFn)) as object[];
70
71
  };
package/constant/types.ts CHANGED
@@ -30,7 +30,11 @@ export type DocumentModel<T> = unknown extends T
30
30
  : Docify<T>;
31
31
 
32
32
  export type FieldState<T> = T extends { id: string } ? T | null : T;
33
- export type DefaultOf<S> = GetStateObject<{ [K in keyof S]: FieldState<S[K]> }>;
33
+ export type DefaultOf<S> = {
34
+ [K in keyof S as S[K] extends (...args: never[]) => unknown ? never : K extends "prototype" ? never : K]: FieldState<
35
+ S[K]
36
+ >;
37
+ };
34
38
 
35
39
  export type DefaultOfSchema<Schema, RelationKey = never> = [RelationKey] extends [never]
36
40
  ? Schema
@@ -27,6 +27,24 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
27
27
  })),
28
28
  pubsubPing: fn(["Pubsub Ping", "Pubsub Ping"]),
29
29
  }))
30
+ .error({
31
+ serverUnreachable: [
32
+ "Cannot reach the server. Check your connection and try again.",
33
+ "서버에 연결할 수 없습니다. 네트워크 상태를 확인한 뒤 다시 시도해주세요.",
34
+ ],
35
+ serverUnavailable: [
36
+ "The server is restarting. Please try again in a moment.",
37
+ "서버가 재시작 중입니다. 잠시 후 다시 시도해주세요.",
38
+ ],
39
+ gatewayTimeout: [
40
+ "The server took too long to answer. Please try again in a moment.",
41
+ "서버 응답이 지연되고 있습니다. 잠시 후 다시 시도해주세요.",
42
+ ],
43
+ unexpectedResponse: [
44
+ "The server returned an unexpected response ({status}).",
45
+ "서버가 예상하지 못한 응답을 보냈습니다. ({status})",
46
+ ],
47
+ })
30
48
  .translate({
31
49
  somethingWrong: ["Something's wrong!", "문제가 생겼어요!"],
32
50
  connecting: ["Connecting...", "연결 중..."],
@@ -1,6 +1,13 @@
1
1
  import { DataList, getEnv, PrimitiveRegistry, type PromiseOrObject } from "akanjs/base";
2
2
  import { capitalize, type FetchPolicy, fileUploadContract, Logger, resolveFileUploadCapability } from "akanjs/common";
3
- import { type BaseInsight, type BaseObject, ConstantRegistry, deserialize, serialize } from "akanjs/constant";
3
+ import {
4
+ type BaseInsight,
5
+ type BaseObject,
6
+ ConstantRegistry,
7
+ deserialize,
8
+ serialize,
9
+ withSharedInstances,
10
+ } from "akanjs/constant";
4
11
  import type {
5
12
  DatabaseSignal,
6
13
  SerializedArg,
@@ -436,6 +443,9 @@ export class FetchClient {
436
443
  const createGuards = signal.createGuards ?? signal.cruGuards;
437
444
  const updateGuards = signal.updateGuards ?? signal.cruGuards;
438
445
  const removeGuards = signal.removeGuards ?? signal.cruGuards;
446
+
447
+ const mcp = (verb: keyof NonNullable<SerializedSignal["mcp"]>) =>
448
+ signal.mcp?.[verb] === false ? { mcp: false as const } : {};
439
449
  const endpoint: { [key: string]: SerializedEndpoint } = {};
440
450
  if (signal.getGuards) {
441
451
  endpoint[names.model] = {
@@ -443,12 +453,14 @@ export class FetchClient {
443
453
  args: [{ type: "param", name: names.modelId, refName: "ID" }],
444
454
  returns: { refName, modelType: "full" },
445
455
  guards: signal.getGuards,
456
+ ...mcp("get"),
446
457
  };
447
458
  endpoint[names.lightModel] = {
448
459
  type: "query",
449
460
  args: [{ type: "param", name: names.modelId, refName: "ID" }],
450
461
  returns: { refName, modelType: "light" },
451
462
  guards: signal.getGuards,
463
+ ...mcp("get"),
452
464
  };
453
465
  }
454
466
  if (createGuards) {
@@ -457,6 +469,7 @@ export class FetchClient {
457
469
  args: [{ type: "body", name: "data", refName, modelType: "input" }],
458
470
  returns: { refName, modelType: "full" },
459
471
  guards: createGuards,
472
+ ...mcp("create"),
460
473
  };
461
474
  }
462
475
  if (updateGuards) {
@@ -468,6 +481,7 @@ export class FetchClient {
468
481
  ],
469
482
  returns: { refName, modelType: "full" },
470
483
  guards: updateGuards,
484
+ ...mcp("update"),
471
485
  };
472
486
  }
473
487
  if (removeGuards) {
@@ -476,6 +490,7 @@ export class FetchClient {
476
490
  args: [{ type: "param", name: names.modelId, refName: "ID" }],
477
491
  returns: { refName, modelType: "full" },
478
492
  guards: removeGuards,
493
+ ...mcp("remove"),
479
494
  };
480
495
  }
481
496
  return endpoint;
@@ -595,18 +610,22 @@ export class FetchClient {
595
610
  list: `${refName}List${capSuffix}`,
596
611
  insight: `${refName}Insight${capSuffix}`,
597
612
  };
613
+
614
+ const mcp = slice.mcp === false ? { mcp: false as const } : {};
598
615
  const endpoint: { [key: string]: SerializedEndpoint } = {
599
616
  [names.list]: {
600
617
  type: "query",
601
618
  args: [...slice.args, ...FetchClient.paginationArgs],
602
619
  returns: { refName, modelType: "light", arrDepth: 1 },
603
620
  guards: slice.guards,
621
+ ...mcp,
604
622
  },
605
623
  [names.insight]: {
606
624
  type: "query",
607
625
  args: [...slice.args],
608
626
  returns: { refName, modelType: "insight" },
609
627
  guards: slice.guards,
628
+ ...mcp,
610
629
  },
611
630
  };
612
631
  return endpoint;
@@ -646,7 +665,9 @@ export class FetchClient {
646
665
  listFn(...fetchQueryArgs, skip, limit, sort, { ...option, crystalize: false }),
647
666
  fetchInsight ? insightFn(...fetchQueryArgs, { ...option, crystalize: false }) : null,
648
667
  ])) as unknown as [BaseObject[], BaseInsight];
649
- const modelList = new DataList(modelObjList.map((modelObj) => new cnst.light(modelObj)));
668
+ const modelList = new DataList(
669
+ withSharedInstances(() => modelObjList.map((modelObj) => new cnst.light(modelObj))),
670
+ );
650
671
  const modelInsight = new cnst.insight(modelObjInsight);
651
672
  const lastPage = modelObjInsight?.count
652
673
  ? Math.max(Math.floor((modelObjInsight.count - 1) / (limit || 20)) + 1, 1)
@@ -27,6 +27,19 @@ interface FetchOptions {
27
27
  baseUrl?: string;
28
28
  }
29
29
 
30
+ const jsonContentType = /^application\/(?:[\w.+-]+\+)?json\b/i;
31
+
32
+ const transportErrorKeyMap = {
33
+ 408: "base.error.gatewayTimeout",
34
+ 502: "base.error.serverUnavailable",
35
+ 503: "base.error.serverUnavailable",
36
+ 504: "base.error.gatewayTimeout",
37
+ } as const;
38
+
39
+ const serverUnreachableKey = "base.error.serverUnreachable";
40
+ const unexpectedResponseKey = "base.error.unexpectedResponse";
41
+ const transportDetailLimit = 200;
42
+
30
43
  export class HttpClient {
31
44
  readonly baseUrl: string;
32
45
  constructor(
@@ -42,11 +55,17 @@ export class HttpClient {
42
55
  #resolveBaseUrl(baseUrl?: string) {
43
56
  return (baseUrl ?? this.baseUrl).replace(/\/$/, "");
44
57
  }
58
+ #resolveUrl(url: string, options: FetchOptions) {
59
+ return `${this.#resolveBaseUrl(options.baseUrl)}${url}`;
60
+ }
61
+
62
+ static #makeHeaders(headers: Record<string, string>, options: FetchOptions) {
63
+ return { Accept: "application/json", ...headers, ...options.headers };
64
+ }
45
65
  async get<Returns = unknown>(url: string, options: FetchOptions = {}): Promise<Returns> {
46
- const res = await fetch(`${this.#resolveBaseUrl(options.baseUrl)}${url}`, {
47
- headers: { "Content-Type": "application/json", ...options.headers },
66
+ return await this.#request<Returns>(this.#resolveUrl(url, options), {
67
+ headers: HttpClient.#makeHeaders({ "Content-Type": "application/json" }, options),
48
68
  });
49
- return await this.#readJsonResponse<Returns>(res);
50
69
  }
51
70
  #makeReqContent(data: FormData | Record<string, unknown>): { body: BodyInit; headers: Record<string, string> } {
52
71
 
@@ -60,12 +79,11 @@ export class HttpClient {
60
79
  options: FetchOptions = {},
61
80
  ): Promise<Returns> {
62
81
  const { body, headers } = this.#makeReqContent(data);
63
- const res = await fetch(`${this.#resolveBaseUrl(options.baseUrl)}${url}`, {
82
+ return await this.#request<Returns>(this.#resolveUrl(url, options), {
64
83
  method,
65
84
  body,
66
- headers: { ...headers, ...options.headers },
85
+ headers: HttpClient.#makeHeaders(headers, options),
67
86
  });
68
- return await this.#readJsonResponse<Returns>(res);
69
87
  }
70
88
  async put<Returns = unknown>(
71
89
  url: string,
@@ -82,19 +100,69 @@ export class HttpClient {
82
100
  return await this.send<Returns>("POST", url, data, options);
83
101
  }
84
102
  async delete<Returns = unknown>(url: string, options: FetchOptions = {}): Promise<Returns> {
85
- const res = await fetch(`${this.#resolveBaseUrl(options.baseUrl)}${url}`, {
103
+ return await this.#request<Returns>(this.#resolveUrl(url, options), {
86
104
  method: "DELETE",
87
- headers: { "Content-Type": "application/json", ...options.headers },
105
+ headers: HttpClient.#makeHeaders({ "Content-Type": "application/json" }, options),
88
106
  });
107
+ }
108
+
109
+ async #request<Returns>(url: string, init: RequestInit): Promise<Returns> {
110
+ const res = await this.#fetch(url, init);
89
111
  return await this.#readJsonResponse<Returns>(res);
90
112
  }
91
113
 
114
+ /**
115
+ * `fetch` rejects only when no response arrived at all — a refused connection, a DNS failure, a socket
116
+ * dropped mid-flight. That is the server being unreachable, not an error this API reported, so it is
117
+ * restored as one rather than surfacing the runtime's own `TypeError: Failed to fetch`.
118
+ */
119
+ async #fetch(url: string, init: RequestInit) {
120
+ try {
121
+ return await fetch(url, init);
122
+ } catch (error) {
123
+ if (error instanceof Error && error.name === "AbortError") throw error;
124
+ throw this.#restoreError({ error: serverUnreachableKey, details: String(error) }, 503);
125
+ }
126
+ }
127
+
92
128
  async #readJsonResponse<Returns>(res: Response): Promise<Returns> {
93
- const body = await res.json();
129
+ const body = await this.#readBody(res);
94
130
  if (res.ok) return body as Returns;
95
131
  throw this.#restoreError(body, res.status);
96
132
  }
97
133
 
134
+ /**
135
+ * A proxy answers a restarting upstream with a page of its own — nginx's `504 Gateway Time-out` HTML,
136
+ * the federation gateway's plain-text 503. That body is not this API's, so parsing it would surface the
137
+ * parser's complaint (`Unexpected token '<'`) instead of the fact that the server is down.
138
+ */
139
+ async #readBody(res: Response) {
140
+ if (jsonContentType.test(res.headers.get("content-type") ?? "")) {
141
+ try {
142
+ return (await res.json()) as unknown;
143
+ } catch (error) {
144
+ throw this.#transportError(res.status, String(error));
145
+ }
146
+ }
147
+ const raw = await res.text();
148
+ const parsed = HttpClient.#parseJson(raw);
149
+ if (parsed === undefined) throw this.#transportError(res.status, raw);
150
+ return parsed;
151
+ }
152
+
153
+ static #parseJson(raw: string): unknown {
154
+ try {
155
+ return JSON.parse(raw) as unknown;
156
+ } catch {
157
+ return undefined;
158
+ }
159
+ }
160
+
161
+ #transportError(status: number, detail: string): RestoredError {
162
+ const error = transportErrorKeyMap[status as keyof typeof transportErrorKeyMap] ?? unexpectedResponseKey;
163
+ return this.#restoreError({ error, data: { status }, details: detail.slice(0, transportDetailLimit) }, status);
164
+ }
165
+
98
166
  #restoreError(body: unknown, fallbackStatusCode: number): RestoredError {
99
167
  const payload =
100
168
  body && typeof body === "object" && "error" in body
@@ -25,33 +25,45 @@ export interface QuerySetting {
25
25
  queryArgs?: unknown[] | (() => unknown[]);
26
26
  }
27
27
 
28
- export type ServerInit<
28
+ type ServerInitShape<
29
29
  RefName extends string,
30
- Light,
31
- Insight = any,
32
- QueryArgs = any,
33
- Filter extends FilterInstance = any,
34
- _CapitalizedRefName extends string = Capitalize<RefName>,
35
- _LightObj = GetStateObject<Light>,
36
- _InsightObj = GetStateObject<Insight>,
37
- _Sort = ExtractSort<Filter>,
30
+ QueryArgs,
31
+ CapRefName extends string,
32
+ LightObj,
33
+ InsightObj,
34
+ Sort,
38
35
  > = SliceMeta & {
39
- [K in `${RefName}ObjList`]: _LightObj[];
36
+ [K in `${RefName}ObjList`]: LightObj[];
40
37
  } & {
41
- [K in `${RefName}ObjInsight`]: _InsightObj;
38
+ [K in `${RefName}ObjInsight`]: InsightObj;
42
39
  } & {
43
- [K in `pageOf${_CapitalizedRefName}`]: number;
40
+ [K in `pageOf${CapRefName}`]: number;
44
41
  } & {
45
- [K in `lastPageOf${_CapitalizedRefName}`]: number;
42
+ [K in `lastPageOf${CapRefName}`]: number;
46
43
  } & {
47
- [K in `limitOf${_CapitalizedRefName}`]: number;
44
+ [K in `limitOf${CapRefName}`]: number;
48
45
  } & {
49
- [K in `queryArgsOf${_CapitalizedRefName}`]: QueryArgs;
46
+ [K in `queryArgsOf${CapRefName}`]: QueryArgs;
50
47
  } & {
51
- [K in `sortOf${_CapitalizedRefName}`]: _Sort;
48
+ [K in `sortOf${CapRefName}`]: Sort;
52
49
  } & {
53
50
  [K in `${RefName}InitAt`]: Date;
54
51
  };
52
+
53
+ export type ServerInit<
54
+ RefName extends string,
55
+ Light,
56
+ Insight = any,
57
+ QueryArgs = any,
58
+ Filter extends FilterInstance = any,
59
+ > = ServerInitShape<
60
+ RefName,
61
+ QueryArgs,
62
+ Capitalize<RefName>,
63
+ GetStateObject<Light>,
64
+ GetStateObject<Insight>,
65
+ ExtractSort<Filter>
66
+ >;
55
67
  /** Client/server-friendly return type for initialized list and insight data. */
56
68
  export type ClientInit<
57
69
  RefName extends string,
@@ -59,13 +71,7 @@ export type ClientInit<
59
71
  Insight = any,
60
72
  QueryArgs = any,
61
73
  Filter extends FilterInstance = any,
62
- _CapitalizedRefName extends string = Capitalize<RefName>,
63
- _LightObj = GetStateObject<Light>,
64
- _InsightObj = GetStateObject<Insight>,
65
- _Sort = ExtractSort<Filter>,
66
- > = PromiseOrObject<
67
- ServerInit<RefName, Light, Insight, QueryArgs, Filter, _CapitalizedRefName, _LightObj, _InsightObj, _Sort>
68
- >;
74
+ > = PromiseOrObject<ServerInit<RefName, Light, Insight, QueryArgs, Filter>>;
69
75
 
70
76
  export type ServerView<RefName extends string, Model> = { refName: RefName } & {
71
77
  [K in `${RefName}Obj`]: GetStateObject<Model>;
@@ -94,6 +100,18 @@ export type EditReturn<RefName extends string, Full> = {
94
100
  [K in `${RefName}Edit`]: ServerEdit<RefName, Full>;
95
101
  };
96
102
 
103
+ type InitReturnShape<
104
+ RefName extends string,
105
+ CapSuffix extends string,
106
+ Init,
107
+ ListItem extends { id: string },
108
+ Insight,
109
+ > = {
110
+ [K in `${RefName}Init${CapSuffix}`]: Init;
111
+ } & {
112
+ [K in `${RefName}List${CapSuffix}`]: DataList<ListItem>;
113
+ } & { [K in `${RefName}Insight${CapSuffix}`]: Insight };
114
+
97
115
  export type InitReturn<
98
116
  RefName extends string,
99
117
  Suffix extends string,
@@ -101,25 +119,11 @@ export type InitReturn<
101
119
  Insight,
102
120
  Args,
103
121
  Filter extends FilterInstance,
104
- _CapitalizedRefName extends string = Capitalize<RefName>,
105
- _CapitalizedSuffix extends string = Capitalize<Suffix>,
106
- _Light extends { id: string } = Light extends { id: string } ? Light : { id: string },
107
- _LightObj = GetStateObject<_Light>,
108
- _InsightObj = GetStateObject<Insight>,
109
- _Sort = ExtractSort<Filter>,
110
- > = {
111
- [K in `${RefName}Init${_CapitalizedSuffix}`]: ServerInit<
112
- RefName,
113
- Light,
114
- Insight,
115
- Args,
116
- Filter,
117
- _CapitalizedRefName,
118
- _LightObj,
119
- _InsightObj,
120
- _Sort
121
- >;
122
- } & {
123
- [K in `${RefName}List${_CapitalizedSuffix}`]: DataList<_Light>;
124
- } & { [K in `${RefName}Insight${_CapitalizedSuffix}`]: Insight };
122
+ > = InitReturnShape<
123
+ RefName,
124
+ Capitalize<Suffix>,
125
+ ServerInit<RefName, Light, Insight, Args, Filter>,
126
+ Light extends { id: string } ? Light : { id: string },
127
+ Insight
128
+ >;
125
129
 
@@ -33,9 +33,12 @@ type EndpointClientReturns<E, SlceCls extends SliceCls | never> = [SlceCls] exte
33
33
  SlceCnstInsight<SlceCls>
34
34
  >;
35
35
 
36
- type EndpInfoReturns<E, SlceCls extends SliceCls | never> =
37
- | EndpointClientReturns<E, SlceCls>
38
- | (EndpInfoNullable<E> extends true ? null : never);
36
+ type OrNull<T, Nullable> = Nullable extends true ? T | null : T;
37
+
38
+ type EndpInfoReturns<E, SlceCls extends SliceCls | never> = OrNull<
39
+ EndpointClientReturns<E, SlceCls>,
40
+ EndpInfoNullable<E>
41
+ >;
39
42
 
40
43
  type QueryOrMutationFetchFn<E, SlceCls extends SliceCls | never> = (
41
44
  ...args: [...EndpInfoArgs<E>, fetchPolicy?: FetchPolicy]
@@ -1,10 +1,9 @@
1
- import type { GetStateObject, SLICE_META } from "akanjs/base";
1
+ import type { SLICE_META } from "akanjs/base";
2
2
  import type { FetchPolicy } from "akanjs/common";
3
3
  import type { ConstantModel, DefaultOf, ProtoFile, PurifiedModel } from "akanjs/constant";
4
4
  import type { DatabaseModel, ExtractSort, FilterInstance } from "akanjs/document";
5
5
  import type {
6
6
  SlceCnstCapitalizedRefName,
7
- SlceCnstDefaultInput,
8
7
  SlceCnstFull,
9
8
  SlceCnstInput,
10
9
  SlceCnstInsight,
@@ -26,11 +25,9 @@ type _Full<S extends SliceCls> = SlceCnstFull<S>;
26
25
  type _Light<S extends SliceCls> = SlceCnstLight<S>;
27
26
  type _Insight<S extends SliceCls> = SlceCnstInsight<S>;
28
27
  type _PurifiedInput<S extends SliceCls> = SlceCnstPurifiedInput<S>;
29
- type _DefaultInput<S extends SliceCls> = SlceCnstDefaultInput<S>;
30
28
  type _Filter<S extends SliceCls> = SlceDbFilter<S>;
31
29
  type _Sort<S extends SliceCls> = SlceDbSort<S>;
32
- type _LightWithId<S extends SliceCls> = _Light<S> extends { id: string } ? _Light<S> : { id: string };
33
- type _SliceFetchInitOption<S extends SliceCls> = FetchInitOption<_Input<S>, _Filter<S>, _DefaultInput<S>, _Sort<S>>;
30
+ type SliceInitOption<S extends SliceCls> = FetchInitOption<_Input<S>, _Filter<S>>;
34
31
 
35
32
  type SliceListFetch<S extends SliceCls> = {
36
33
  [Suffix in keyof _SliceMap<S> as Suffix extends string ? `${_RefName<S>}List${Capitalize<Suffix>}` : never]: (
@@ -50,43 +47,33 @@ type SliceInsightFetch<S extends SliceCls> = {
50
47
  ) => Promise<_Insight<S>>;
51
48
  };
52
49
 
50
+ type SliceInit<S extends SliceCls, Suffix extends keyof _SliceMap<S>> = InitReturn<
51
+ _RefName<S>,
52
+ Suffix & string,
53
+ _Light<S>,
54
+ _Insight<S>,
55
+ SliceInfoArgs<_SliceMap<S>[Suffix]>,
56
+ _Filter<S>
57
+ >;
58
+
59
+ type SliceServerInit<S extends SliceCls, Suffix extends keyof _SliceMap<S>> = ServerInit<
60
+ _RefName<S>,
61
+ _Light<S>,
62
+ _Insight<S>,
63
+ SliceInfoArgs<_SliceMap<S>[Suffix]>,
64
+ _Filter<S>
65
+ >;
66
+
53
67
  type SliceInitFetch<S extends SliceCls> = {
54
68
  [Suffix in keyof _SliceMap<S> as Suffix extends string ? `init${_Cap<S>}${Capitalize<Suffix>}` : never]: (
55
- ...args: [...SliceInfoArgs<_SliceMap<S>[Suffix]>, option?: _SliceFetchInitOption<S>]
56
- ) => Promise<
57
- InitReturn<
58
- _RefName<S>,
59
- Suffix & string,
60
- _Light<S>,
61
- _Insight<S>,
62
- SliceInfoArgs<_SliceMap<S>[Suffix]>,
63
- _Filter<S>,
64
- _Cap<S>,
65
- Suffix extends string ? Capitalize<Suffix> : never,
66
- _LightWithId<S>,
67
- GetStateObject<_LightWithId<S>>,
68
- GetStateObject<_Insight<S>>,
69
- _Sort<S>
70
- >
71
- >;
69
+ ...args: [...SliceInfoArgs<_SliceMap<S>[Suffix]>, option?: SliceInitOption<S>]
70
+ ) => Promise<SliceInit<S, Suffix>>;
72
71
  };
73
72
 
74
73
  type SliceGetInitFetch<S extends SliceCls> = {
75
74
  [Suffix in keyof _SliceMap<S> as Suffix extends string ? `get${_Cap<S>}Init${Capitalize<Suffix>}` : never]: (
76
- ...args: [...SliceInfoArgs<_SliceMap<S>[Suffix]>, option?: _SliceFetchInitOption<S>]
77
- ) => Promise<
78
- ServerInit<
79
- _RefName<S>,
80
- _Light<S>,
81
- _Insight<S>,
82
- SliceInfoArgs<_SliceMap<S>[Suffix]>,
83
- _Filter<S>,
84
- _Cap<S>,
85
- GetStateObject<_LightWithId<S>>,
86
- GetStateObject<_Insight<S>>,
87
- _Sort<S>
88
- >
89
- >;
75
+ ...args: [...SliceInfoArgs<_SliceMap<S>[Suffix]>, option?: SliceInitOption<S>]
76
+ ) => Promise<SliceServerInit<S, Suffix>>;
90
77
  };
91
78
 
92
79
  export type GetFetchTypeFromSlice<SlceCls extends SliceCls> = SlceCls["srv"]["cnst"] extends ConstantModel
@@ -164,23 +151,13 @@ type AppliedBaseSliceFetchType<
164
151
  ) => Promise<Full>;
165
152
  };
166
153
 
167
- export interface FetchInitForm<
168
- Input,
169
- Filter extends FilterInstance,
170
- _DefaultInput = DefaultOf<Input>,
171
- _Sort = ExtractSort<Filter>,
172
- > {
154
+ export interface FetchInitForm<Input, Filter extends FilterInstance> {
173
155
  page?: number;
174
156
  limit?: number;
175
- sort?: _Sort;
176
- default?: Partial<_DefaultInput>;
157
+ sort?: ExtractSort<Filter>;
158
+ default?: Partial<DefaultOf<Input>>;
177
159
  invalidate?: boolean;
178
160
  insight?: boolean;
179
161
  }
180
162
 
181
- type FetchInitOption<
182
- Input,
183
- Filter extends FilterInstance,
184
- _DefaultInput = DefaultOf<Input>,
185
- _Sort = ExtractSort<Filter>,
186
- > = FetchPolicy & FetchInitForm<Input, Filter, _DefaultInput, _Sort>;
163
+ type FetchInitOption<Input, Filter extends FilterInstance> = FetchPolicy & FetchInitForm<Input, Filter>;