akanjs 2.1.1-rc.1 → 2.1.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.
- package/client/router.ts +14 -2
- package/constant/purify.ts +80 -23
- package/package.json +1 -1
- package/ui/System/Client.tsx +4 -2
package/client/router.ts
CHANGED
|
@@ -64,6 +64,11 @@ const getConfiguredBasePaths = () => new Set(parseBasePaths(process.env.AKAN_PUB
|
|
|
64
64
|
|
|
65
65
|
const shouldExposeBasePath = () => getEnv().operationMode === "local";
|
|
66
66
|
|
|
67
|
+
const getLocaleFromPathname = (pathname: string) => {
|
|
68
|
+
const [firstSegment] = pathname.split("/").filter(Boolean);
|
|
69
|
+
return parseAkanI18nEnv().locales.find((locale) => locale === firstSegment);
|
|
70
|
+
};
|
|
71
|
+
|
|
67
72
|
const getServerBasePath = (reqPathname: string, lang: string, headerBasePath: string | undefined, fallback: string) => {
|
|
68
73
|
return (
|
|
69
74
|
getBasePathFromPathname(reqPathname, {
|
|
@@ -195,6 +200,9 @@ class Router {
|
|
|
195
200
|
#getNavigationPathInfo(href: string) {
|
|
196
201
|
return this.#getPathInfo(href, shouldExposeBasePath() ? this.#prefix : "");
|
|
197
202
|
}
|
|
203
|
+
#getVisiblePathInfo(href: string, lang = this.#lang) {
|
|
204
|
+
return getPathInfo(href, lang, shouldExposeBasePath() ? this.#prefix : "");
|
|
205
|
+
}
|
|
198
206
|
#postPathChange({ path, pathname, hash }: { path: string; pathname: string; hash: string }) {
|
|
199
207
|
Logger.log(`pathChange-start:${path}${hash ? `#${hash}` : ""}`);
|
|
200
208
|
window.parent.postMessage({ type: "pathChange", path, pathname, hash }, "*");
|
|
@@ -251,9 +259,13 @@ class Router {
|
|
|
251
259
|
setLang(lang: string) {
|
|
252
260
|
if (getEnv().side === "server") throw new Error("setLang is only available in client side");
|
|
253
261
|
this.#checkInitialized();
|
|
254
|
-
const
|
|
262
|
+
const currentLang = getLocaleFromPathname(window.location.pathname) ?? this.#lang;
|
|
263
|
+
const { path, search, hash } = this.#getVisiblePathInfo(
|
|
264
|
+
`${window.location.pathname}${window.location.search ?? ""}${window.location.hash ?? ""}`,
|
|
265
|
+
currentLang,
|
|
266
|
+
);
|
|
255
267
|
this.#lang = lang;
|
|
256
|
-
this.#instance.replace(
|
|
268
|
+
this.#instance.replace(`${path}${search ? `?${search}` : ""}${hash ? `#${hash}` : ""}`);
|
|
257
269
|
return undefined as never;
|
|
258
270
|
}
|
|
259
271
|
getPath(pathname = window.location.pathname) {
|
package/constant/purify.ts
CHANGED
|
@@ -13,7 +13,13 @@ import {
|
|
|
13
13
|
} from "akanjs/base";
|
|
14
14
|
import { Logger } from "akanjs/common";
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type BaseObject,
|
|
18
|
+
type ConstantCls,
|
|
19
|
+
ConstantRegistry,
|
|
20
|
+
type DefaultOf,
|
|
21
|
+
type FieldProps,
|
|
22
|
+
} from ".";
|
|
17
23
|
|
|
18
24
|
type Purified<O> = O extends BaseObject
|
|
19
25
|
? string
|
|
@@ -24,10 +30,15 @@ type Purified<O> = O extends BaseObject
|
|
|
24
30
|
: O extends object
|
|
25
31
|
? PurifiedModel<O>
|
|
26
32
|
: O;
|
|
27
|
-
type PurifiedWithObjectToId<
|
|
33
|
+
type PurifiedWithObjectToId<
|
|
34
|
+
T,
|
|
35
|
+
StateKeys extends keyof GetStateObject<T> = keyof GetStateObject<T>,
|
|
36
|
+
> = {
|
|
28
37
|
[K in StateKeys as null extends T[K] ? never : K]: Purified<T[K]>;
|
|
29
38
|
} & {
|
|
30
|
-
[K in StateKeys as null extends T[K] ? K : never]?:
|
|
39
|
+
[K in StateKeys as null extends T[K] ? K : never]?:
|
|
40
|
+
| Purified<T[K]>
|
|
41
|
+
| undefined;
|
|
31
42
|
};
|
|
32
43
|
export type PurifiedModel<T> = T extends Upload[]
|
|
33
44
|
? FileList
|
|
@@ -39,20 +50,29 @@ export type PurifiedModel<T> = T extends Upload[]
|
|
|
39
50
|
? Map<K, PurifiedModel<V>>
|
|
40
51
|
: PurifiedWithObjectToId<T>;
|
|
41
52
|
|
|
42
|
-
export type PurifyFunc<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
export type PurifyFunc<
|
|
54
|
+
Input,
|
|
55
|
+
_DefaultInput = DefaultOf<Input>,
|
|
56
|
+
_PurifiedInput = PurifiedModel<Input>,
|
|
57
|
+
> = (self: _DefaultInput, isChild?: boolean) => _PurifiedInput | null;
|
|
46
58
|
|
|
47
59
|
const getPurifyFn = (modelRef: Cls): ((value: unknown) => unknown) => {
|
|
48
60
|
const [valueRef] = getNonArrayModel(modelRef);
|
|
49
61
|
const purifyFn = PrimitiveRegistry.has(valueRef)
|
|
50
|
-
? (value: unknown) =>
|
|
62
|
+
? (value: unknown) =>
|
|
63
|
+
(valueRef as unknown as typeof PrimitiveScalar)._serialize(
|
|
64
|
+
value as never,
|
|
65
|
+
)
|
|
51
66
|
: (value: unknown) => value as object;
|
|
52
67
|
return purifyFn;
|
|
53
68
|
};
|
|
54
69
|
|
|
55
|
-
const purify = (
|
|
70
|
+
const purify = (
|
|
71
|
+
field: FieldProps,
|
|
72
|
+
key: string,
|
|
73
|
+
value: unknown,
|
|
74
|
+
self: Record<string, unknown>,
|
|
75
|
+
): unknown => {
|
|
56
76
|
|
|
57
77
|
if (
|
|
58
78
|
field.nullable &&
|
|
@@ -63,30 +83,67 @@ const purify = (field: FieldProps, key: string, value: unknown, self: Record<str
|
|
|
63
83
|
)
|
|
64
84
|
return null;
|
|
65
85
|
if (field.isArray) {
|
|
66
|
-
if (!Array.isArray(value))
|
|
86
|
+
if (!Array.isArray(value))
|
|
87
|
+
throw new Error(`Invalid Array Value in ${key} for value ${value}`);
|
|
67
88
|
if (field.minlength && value.length < field.minlength)
|
|
68
|
-
throw new Error(
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Invalid Array Length (Min) in ${key} for value ${value}`,
|
|
91
|
+
);
|
|
69
92
|
else if (field.maxlength && value.length > field.maxlength)
|
|
70
|
-
throw new Error(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Invalid Array Length (Max) in ${key} for value ${value}`,
|
|
95
|
+
);
|
|
96
|
+
else if (
|
|
97
|
+
field.optArrDepth === 0 &&
|
|
98
|
+
field.validate &&
|
|
99
|
+
!field.validate(value, self)
|
|
100
|
+
)
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Invalid Array Value (Failed to pass validation) in ${key} for value ${value}`,
|
|
103
|
+
);
|
|
104
|
+
return value.map((v) =>
|
|
105
|
+
purify(
|
|
106
|
+
{ ...field, isArray: field.arrDepth > 1, arrDepth: field.arrDepth - 1 },
|
|
107
|
+
key,
|
|
108
|
+
v,
|
|
109
|
+
v,
|
|
110
|
+
),
|
|
111
|
+
);
|
|
74
112
|
}
|
|
75
113
|
if (field.isMap && field.of) {
|
|
76
114
|
const purifyFn = PrimitiveRegistry.has(field.of as Cls)
|
|
77
115
|
? getPurifyFn(field.of as Cls)
|
|
78
|
-
: (value: unknown) =>
|
|
116
|
+
: (value: unknown) =>
|
|
117
|
+
makePurify(field.of as ConstantCls)(value as object);
|
|
79
118
|
return Object.fromEntries(
|
|
80
|
-
[...(value as Map<string, unknown>).entries()].map(([key, val]) => [
|
|
119
|
+
[...(value as Map<string, unknown>).entries()].map(([key, val]) => [
|
|
120
|
+
key,
|
|
121
|
+
applyFnToArrayObjects(val, purifyFn),
|
|
122
|
+
]),
|
|
81
123
|
);
|
|
82
124
|
}
|
|
83
|
-
if (field.isClass)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
125
|
+
if (field.isClass)
|
|
126
|
+
return makePurify(field.modelRef)(value as object, true) as object;
|
|
127
|
+
if (
|
|
128
|
+
field.modelRef === Date &&
|
|
129
|
+
dayjs(value as Date).isBefore(dayjs(new Date("0000")))
|
|
130
|
+
)
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Invalid Date Value (Default) in ${key} for value ${value}`,
|
|
133
|
+
);
|
|
134
|
+
if (
|
|
135
|
+
[String, ID].includes(
|
|
136
|
+
field.modelRef as unknown as StringConstructor | typeof ID,
|
|
137
|
+
) &&
|
|
138
|
+
(value === "" || !value)
|
|
139
|
+
)
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Invalid String Value (Default) in ${key} for value ${value}`,
|
|
142
|
+
);
|
|
88
143
|
if (field.validate && !field.validate(value, self))
|
|
89
|
-
throw new Error(
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Invalid Value (Failed to pass validation) / ${value} in ${key}`,
|
|
146
|
+
);
|
|
90
147
|
if (!field.nullable && !value && value !== 0 && value !== false)
|
|
91
148
|
throw new Error(`Invalid Value (Nullable) in ${key} for value ${value}`);
|
|
92
149
|
|
package/package.json
CHANGED
package/ui/System/Client.tsx
CHANGED
|
@@ -241,6 +241,7 @@ interface ClientSsrBridgeProps {
|
|
|
241
241
|
}
|
|
242
242
|
export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) => {
|
|
243
243
|
useEffect(() => {
|
|
244
|
+
const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
|
|
244
245
|
const navigateRscWithFallback = (
|
|
245
246
|
href: string,
|
|
246
247
|
routeOptions: Parameters<typeof navigateRsc>[1],
|
|
@@ -258,7 +259,7 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
|
|
|
258
259
|
};
|
|
259
260
|
const syncHref = (href: string) => {
|
|
260
261
|
const url = new URL(href, window.location.origin);
|
|
261
|
-
const { path } = getPathInfo(`${url.pathname}${url.search}${url.hash}`, lang,
|
|
262
|
+
const { path } = getPathInfo(`${url.pathname}${url.search}${url.hash}`, lang, visiblePrefix);
|
|
262
263
|
const searchParams = buildSearchParams(url.searchParams.entries());
|
|
263
264
|
st.set({ pathname: url.pathname, path, searchParams });
|
|
264
265
|
};
|
|
@@ -290,9 +291,10 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
|
|
|
290
291
|
}, [lang, prefix]);
|
|
291
292
|
|
|
292
293
|
useEffect(() => {
|
|
294
|
+
const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
|
|
293
295
|
const sync = () => {
|
|
294
296
|
const { pathname, search, hash } = window.location;
|
|
295
|
-
const { path } = getPathInfo(`${pathname}${search}${hash}`, lang,
|
|
297
|
+
const { path } = getPathInfo(`${pathname}${search}${hash}`, lang, visiblePrefix);
|
|
296
298
|
const searchParams = buildSearchParams(new URLSearchParams(search).entries());
|
|
297
299
|
st.set({ pathname: window.location.pathname, path, searchParams });
|
|
298
300
|
};
|