akanjs 2.3.11-rc.0 → 2.3.11-rc.10
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/capacitor.ts +13 -0
- package/client/csrTypes.ts +2 -0
- package/common/routeConvention.ts +8 -5
- package/document/documentQuery.ts +55 -9
- package/document/into.ts +11 -7
- package/package.json +5 -1
- package/server/proxy/localeWebProxy.ts +10 -1
- package/server/resolver/database.resolver.ts +4 -3
- package/server/resolver/signal.resolver.ts +18 -2
- package/server/routeTreeBuilder.ts +37 -4
- package/service/predefinedAdaptor/database.adaptor.ts +558 -170
- package/types/client/capacitor.d.ts +11 -0
- package/types/client/csrTypes.d.ts +2 -0
- package/types/common/routeConvention.d.ts +1 -1
- package/types/document/documentQuery.d.ts +38 -9
- package/types/document/into.d.ts +7 -7
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +105 -17
- package/types/ui/Button.d.ts +6 -1
- package/types/ui/DatePicker.d.ts +12 -9
- package/types/ui/Dropdown.d.ts +6 -1
- package/types/ui/Empty.d.ts +7 -1
- package/types/ui/Input.d.ts +12 -8
- package/types/ui/Loading/index.d.ts +11 -6
- package/types/ui/Menu.d.ts +9 -4
- package/types/ui/Modal.d.ts +11 -1
- package/types/ui/Pagination.d.ts +6 -1
- package/types/ui/Popconfirm.d.ts +7 -1
- package/types/ui/Radio.d.ts +7 -4
- package/types/ui/Select.d.ts +6 -1
- package/types/ui/Table.d.ts +6 -1
- package/types/ui/ToggleSelect.d.ts +11 -7
- package/types/ui/UiOverride/Provider.d.ts +14 -0
- package/types/ui/UiOverride/context.d.ts +72 -0
- package/types/ui/UiOverride/createOverridable.d.ts +9 -0
- package/types/ui/UiOverride/index.d.ts +5 -0
- package/types/ui/UiOverride/override.d.ts +18 -0
- package/types/ui/UiOverride/useUiOverride.d.ts +7 -0
- package/types/ui/UiOverride.d.ts +1 -0
- package/types/ui/Unauthorized.d.ts +8 -3
- package/types/ui/index.d.ts +1 -0
- package/types/webkit/index.d.ts +1 -1
- package/types/webkit/usePushNotification.d.ts +28 -0
- package/ui/Button.tsx +15 -2
- package/ui/DatePicker.tsx +19 -9
- package/ui/Dropdown.tsx +9 -1
- package/ui/Empty.tsx +11 -1
- package/ui/Input.tsx +22 -14
- package/ui/Loading/index.tsx +15 -1
- package/ui/Menu.tsx +12 -3
- package/ui/Modal.tsx +13 -1
- package/ui/Pagination.tsx +9 -1
- package/ui/Popconfirm.tsx +9 -1
- package/ui/Radio.tsx +14 -3
- package/ui/Select.tsx +22 -2
- package/ui/System/CSR.tsx +1 -9
- package/ui/System/Client.tsx +1 -0
- package/ui/Table.tsx +8 -1
- package/ui/ToggleSelect.tsx +31 -5
- package/ui/UiOverride/Provider.tsx +22 -0
- package/ui/UiOverride/context.ts +85 -0
- package/ui/UiOverride/createOverridable.tsx +25 -0
- package/ui/UiOverride/index.ts +5 -0
- package/ui/UiOverride/override.ts +19 -0
- package/ui/UiOverride/useUiOverride.ts +15 -0
- package/ui/Unauthorized.tsx +12 -3
- package/ui/index.ts +10 -0
- package/webkit/bootCsr.tsx +28 -5
- package/webkit/index.ts +8 -1
- package/webkit/usePushNotification.tsx +267 -0
- package/types/webkit/usePushNoti.d.ts +0 -7
- package/webkit/usePushNoti.tsx +0 -48
package/client/capacitor.ts
CHANGED
|
@@ -107,6 +107,19 @@ export type CapacitorPushNotificationsModule = {
|
|
|
107
107
|
requestPermissions: () => Promise<{ receive: "granted" | "denied" | string }>;
|
|
108
108
|
checkPermissions: () => Promise<{ receive: "granted" | "denied" | string }>;
|
|
109
109
|
register: () => Promise<void> | void;
|
|
110
|
+
addListener: (
|
|
111
|
+
eventName:
|
|
112
|
+
| "registration"
|
|
113
|
+
| "registrationError"
|
|
114
|
+
| "pushNotificationReceived"
|
|
115
|
+
| "pushNotificationActionPerformed"
|
|
116
|
+
| string,
|
|
117
|
+
listenerFunc: (event: {
|
|
118
|
+
value?: string;
|
|
119
|
+
error?: string;
|
|
120
|
+
notification?: { data?: Record<string, unknown> };
|
|
121
|
+
}) => void,
|
|
122
|
+
) => Promise<{ remove?: () => Promise<void> | void } | void> | { remove?: () => Promise<void> | void } | void;
|
|
110
123
|
};
|
|
111
124
|
};
|
|
112
125
|
|
package/client/csrTypes.ts
CHANGED
|
@@ -191,6 +191,8 @@ export interface Route {
|
|
|
191
191
|
path: string;
|
|
192
192
|
renderPage?: RouteRender;
|
|
193
193
|
renderLayout?: RouteRender;
|
|
194
|
+
/** Synthetic layout render from a `_overrides.tsx` at this node; wraps the subtree in a UI-override provider. */
|
|
195
|
+
renderOverrides?: RouteRender;
|
|
194
196
|
pageIncludesOwnLayout?: boolean;
|
|
195
197
|
isSpecialRoute?: boolean;
|
|
196
198
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const ROUTE_SOURCE_RE = /^\.\/(.+)\.(tsx|ts|jsx|js)$/;
|
|
2
2
|
const SOURCE_EXT_RE = /\.(tsx|ts|jsx|js)$/;
|
|
3
|
-
const RESERVED_ROUTE_FILES = new Set(["_layout", "_index"]);
|
|
3
|
+
const RESERVED_ROUTE_FILES = new Set(["_layout", "_index", "_overrides"]);
|
|
4
4
|
const INTERNAL_ROOT_LAYOUT_LEAF = "__root_layout";
|
|
5
5
|
const IMPLICIT_LOCALE_SEGMENT = "[lang]";
|
|
6
6
|
const SPECIAL_ROUTE_LEAVES = new Set(["robots.txt"]);
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const DIRECTORY_SCOPED_LEAVES = new Set(["_layout", "_index", "_overrides"]);
|
|
9
|
+
|
|
10
|
+
export type RouteModuleKind = "page" | "layout" | "overrides";
|
|
9
11
|
|
|
10
12
|
export interface ParsedRouteModuleKey {
|
|
11
13
|
key: string;
|
|
@@ -51,7 +53,7 @@ export function validatePageSourceFile(filePath: string, options: ValidatePageSo
|
|
|
51
53
|
if (ext !== "tsx") throw new Error(`[route-convention] route source files under page/ must use .tsx: ${displayPath}`);
|
|
52
54
|
if (leaf.startsWith("_") && !RESERVED_ROUTE_FILES.has(leaf) && leaf !== INTERNAL_ROOT_LAYOUT_LEAF)
|
|
53
55
|
throw new Error(
|
|
54
|
-
`[route-convention] only _index.tsx and
|
|
56
|
+
`[route-convention] only _index.tsx, _layout.tsx and _overrides.tsx are allowed as reserved route files under page/: ${displayPath}`,
|
|
55
57
|
);
|
|
56
58
|
if (/^[A-Z]/.test(leaf))
|
|
57
59
|
throw new Error(`[route-convention] route page filenames must not start with an uppercase letter: ${displayPath}`);
|
|
@@ -97,13 +99,14 @@ export function parseRouteModuleKey(key: string): ParsedRouteModuleKey {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
const isInternalRootLayout = leaf === INTERNAL_ROOT_LAYOUT_LEAF;
|
|
100
|
-
const kind: RouteModuleKind =
|
|
102
|
+
const kind: RouteModuleKind =
|
|
103
|
+
leaf === "_layout" || isInternalRootLayout ? "layout" : leaf === "_overrides" ? "overrides" : "page";
|
|
101
104
|
if (leaf.startsWith("_") && !RESERVED_ROUTE_FILES.has(leaf) && !isInternalRootLayout) {
|
|
102
105
|
throw new Error(`[route-convention] unsupported reserved route file "${leaf}" in ${key}`);
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
const sourceRouteSegments =
|
|
106
|
-
leaf
|
|
109
|
+
DIRECTORY_SCOPED_LEAVES.has(leaf) || isInternalRootLayout ? moduleSegments.slice(0, -1) : moduleSegments;
|
|
107
110
|
const isSpecialRoute = kind === "page" && SPECIAL_ROUTE_LEAVES.has(leaf);
|
|
108
111
|
const routeSegments = isSpecialRoute ? sourceRouteSegments : [IMPLICIT_LOCALE_SEGMENT, ...sourceRouteSegments];
|
|
109
112
|
for (const segment of routeSegments) validateRouteSegment(segment, key);
|
|
@@ -53,21 +53,67 @@ export type DocumentQueryValue =
|
|
|
53
53
|
| undefined
|
|
54
54
|
| Record<string, unknown>;
|
|
55
55
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
export type DocumentUpdateOperator =
|
|
57
|
+
| "set"
|
|
58
|
+
| "unset"
|
|
59
|
+
| "inc"
|
|
60
|
+
| "mul"
|
|
61
|
+
| "min"
|
|
62
|
+
| "max"
|
|
63
|
+
| "push"
|
|
64
|
+
| "pull"
|
|
65
|
+
| "addToSet"
|
|
66
|
+
| "setOnInsert";
|
|
67
|
+
|
|
68
|
+
export interface DocumentUpdateNode {
|
|
69
|
+
kind: "update";
|
|
70
|
+
op: DocumentUpdateOperator;
|
|
71
|
+
value?: unknown;
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
export type DocumentUpdateValue =
|
|
75
|
+
| DocumentUpdateNode
|
|
76
|
+
| DocumentPrimitive
|
|
77
|
+
| DocumentPrimitive[]
|
|
78
|
+
| Record<string, unknown>
|
|
79
|
+
| undefined;
|
|
80
|
+
|
|
81
|
+
export type DocumentUpdate<T = any> = {
|
|
82
|
+
[K in DocumentPath<T>]?: DocumentUpdateValue;
|
|
83
|
+
};
|
|
84
|
+
|
|
67
85
|
export interface DocumentUpdateOptions {
|
|
68
86
|
upsert?: boolean;
|
|
69
87
|
}
|
|
70
88
|
|
|
89
|
+
const updateOp = (op: DocumentUpdateOperator, value?: unknown): DocumentUpdateNode => ({ kind: "update", op, value });
|
|
90
|
+
|
|
91
|
+
export const createDocumentUpdateHelper = () => ({
|
|
92
|
+
set: (value: unknown) => updateOp("set", value),
|
|
93
|
+
unset: () => updateOp("unset"),
|
|
94
|
+
inc: (by = 1) => updateOp("inc", by),
|
|
95
|
+
mul: (by: number) => updateOp("mul", by),
|
|
96
|
+
min: (value: unknown) => updateOp("min", value),
|
|
97
|
+
max: (value: unknown) => updateOp("max", value),
|
|
98
|
+
push: (value: unknown) => updateOp("push", value),
|
|
99
|
+
pull: (value: unknown) => updateOp("pull", value),
|
|
100
|
+
addToSet: (value: unknown) => updateOp("addToSet", value),
|
|
101
|
+
setOnInsert: (value: unknown) => updateOp("setOnInsert", value),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export type DocumentUpdateHelper = ReturnType<typeof createDocumentUpdateHelper>;
|
|
105
|
+
|
|
106
|
+
export const documentUpdateHelper = createDocumentUpdateHelper();
|
|
107
|
+
|
|
108
|
+
export type DocumentUpdateBuilder<T = any> = (helper: DocumentUpdateHelper) => DocumentUpdate<T>;
|
|
109
|
+
export type DocumentUpdateInput<T = any> = DocumentUpdate<T> | DocumentUpdateBuilder<T>;
|
|
110
|
+
|
|
111
|
+
export const resolveDocumentUpdate = <T>(update: DocumentUpdateInput<T>): DocumentUpdate<T> =>
|
|
112
|
+
typeof update === "function" ? update(documentUpdateHelper) : update;
|
|
113
|
+
|
|
114
|
+
export const isDocumentUpdateNode = (value: unknown): value is DocumentUpdateNode =>
|
|
115
|
+
!!value && typeof value === "object" && (value as { kind?: unknown }).kind === "update";
|
|
116
|
+
|
|
71
117
|
export type DocumentQuery<T = any> =
|
|
72
118
|
| DocumentQueryNode
|
|
73
119
|
| {
|
package/document/into.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { DocumentModel, QueryOf } from "akanjs/constant";
|
|
|
4
4
|
import type { FilterCls, FilterQueryOf, FilterSortOf, SchemaOf } from ".";
|
|
5
5
|
import type { CacheDatabase, QueryMethodPart } from "./database";
|
|
6
6
|
import type { DataLoader } from "./dataLoader";
|
|
7
|
-
import type {
|
|
7
|
+
import type { DocumentQuery, DocumentUpdateInput, DocumentUpdateOptions } from "./documentQuery";
|
|
8
8
|
import { type LoaderBuilder, type ModelCls, makeLoaderBuilder } from "./loaderInfo";
|
|
9
9
|
import type { DocumentProjection } from "./types";
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ interface DefaultMdlStats<
|
|
|
15
15
|
TDocument,
|
|
16
16
|
TSchema,
|
|
17
17
|
_Partial extends Partial<TSchema> = Partial<TSchema>,
|
|
18
|
-
_FilterQuery extends
|
|
18
|
+
_FilterQuery extends DocumentQuery<TSchema> = DocumentQuery<TSchema>,
|
|
19
19
|
_Projection = DocumentProjection<TSchema>,
|
|
20
20
|
> {
|
|
21
21
|
pickOneAndWrite: (query: _FilterQuery, rawData: _Partial) => Promise<TDocument>;
|
|
@@ -47,10 +47,10 @@ export interface UpdateResult {
|
|
|
47
47
|
modifiedCount: number;
|
|
48
48
|
upsertedId?: string | null;
|
|
49
49
|
}
|
|
50
|
-
export interface BulkWriteOperation<Raw, _RawDoc = DocumentModel<Raw>, _RawQuery =
|
|
50
|
+
export interface BulkWriteOperation<Raw, _RawDoc = DocumentModel<Raw>, _RawQuery = DocumentQuery<_RawDoc>> {
|
|
51
51
|
updateOne: {
|
|
52
52
|
filter: _RawQuery;
|
|
53
|
-
update:
|
|
53
|
+
update: DocumentUpdateInput<_RawDoc>;
|
|
54
54
|
upsert?: boolean;
|
|
55
55
|
};
|
|
56
56
|
}
|
|
@@ -69,7 +69,7 @@ export type Mdl<
|
|
|
69
69
|
Doc,
|
|
70
70
|
Raw,
|
|
71
71
|
_RawDoc = DocumentModel<Raw>,
|
|
72
|
-
_RawQuery =
|
|
72
|
+
_RawQuery extends DocumentQuery<_RawDoc> = DocumentQuery<_RawDoc>,
|
|
73
73
|
_Projection extends DocumentProjection<Raw> = DocumentProjection<Raw>,
|
|
74
74
|
> = DefaultMdlStats<Doc, _RawDoc, Partial<_RawDoc>, _RawQuery, _Projection> & {
|
|
75
75
|
refName: string;
|
|
@@ -79,8 +79,12 @@ export type Mdl<
|
|
|
79
79
|
findById(id: string | undefined, projection?: _Projection): Promise<Doc | null>;
|
|
80
80
|
countDocuments(query: _RawQuery): Promise<number>;
|
|
81
81
|
exists(query: _RawQuery): Promise<string | null>;
|
|
82
|
-
updateOne(
|
|
83
|
-
|
|
82
|
+
updateOne(
|
|
83
|
+
query: _RawQuery,
|
|
84
|
+
update: DocumentUpdateInput<_RawDoc>,
|
|
85
|
+
options?: DocumentUpdateOptions,
|
|
86
|
+
): Promise<UpdateResult>;
|
|
87
|
+
updateMany(query: _RawQuery, update: DocumentUpdateInput<_RawDoc>): Promise<UpdateResult>;
|
|
84
88
|
deleteMany(query: _RawQuery): Promise<UpdateResult>;
|
|
85
89
|
bulkWrite(operations: BulkWriteOperation<Raw, _RawDoc, _RawQuery>[]): Promise<UpdateResult>;
|
|
86
90
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akanjs",
|
|
3
|
-
"version": "2.3.11-rc.
|
|
3
|
+
"version": "2.3.11-rc.10",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -198,6 +198,7 @@
|
|
|
198
198
|
"cordova-plugin-purchase": "^13.16.0",
|
|
199
199
|
"croner": "^10.0.1",
|
|
200
200
|
"daisyui": "5.5.23",
|
|
201
|
+
"firebase": "^12.13.0",
|
|
201
202
|
"ioredis": "^5.10.1",
|
|
202
203
|
"mermaid": "^11.15.0",
|
|
203
204
|
"postgres": "^3.4.9",
|
|
@@ -288,6 +289,9 @@
|
|
|
288
289
|
"daisyui": {
|
|
289
290
|
"optional": true
|
|
290
291
|
},
|
|
292
|
+
"firebase": {
|
|
293
|
+
"optional": true
|
|
294
|
+
},
|
|
291
295
|
"ioredis": {
|
|
292
296
|
"optional": true
|
|
293
297
|
},
|
|
@@ -49,7 +49,12 @@ export class LocaleWebProxy implements WebProxy {
|
|
|
49
49
|
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
if (
|
|
52
|
+
if (
|
|
53
|
+
!isInternalProxyRequest(requestUrl) &&
|
|
54
|
+
!isWellKnownRequest(pathname) &&
|
|
55
|
+
!isApiRequest(pathname) &&
|
|
56
|
+
pathnameIsMissingLocale
|
|
57
|
+
) {
|
|
53
58
|
return Response.redirect(
|
|
54
59
|
new URL(`/${getLocale(request)}/${pathname.slice(1)}${targetUrl.search}`, getPublicRequestUrl(request)),
|
|
55
60
|
307,
|
|
@@ -90,3 +95,7 @@ function isInternalProxyRequest(requestUrl: URL): boolean {
|
|
|
90
95
|
function isWellKnownRequest(pathname: string): boolean {
|
|
91
96
|
return pathname === "/.well-known" || pathname.startsWith("/.well-known/");
|
|
92
97
|
}
|
|
98
|
+
|
|
99
|
+
function isApiRequest(pathname: string): boolean {
|
|
100
|
+
return pathname === "/api" || pathname.startsWith("/api/");
|
|
101
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type DataInputOf,
|
|
10
10
|
DataLoader,
|
|
11
11
|
DocumentSchema,
|
|
12
|
+
type DocumentUpdateInput,
|
|
12
13
|
documentQueryHelper,
|
|
13
14
|
type FindQueryOption,
|
|
14
15
|
fillMissingFilterArgs,
|
|
@@ -219,12 +220,12 @@ export class DatabaseResolver {
|
|
|
219
220
|
findOne: (query: QueryOf<any>) => createFindOneChain(query),
|
|
220
221
|
findById: (id: string | undefined) => (id ? store.findOne({ id }) : Promise.resolve(null)),
|
|
221
222
|
countDocuments: (query: QueryOf<any>) => store.count(query),
|
|
222
|
-
updateOne: (query: QueryOf<any>, update:
|
|
223
|
+
updateOne: (query: QueryOf<any>, update: DocumentUpdateInput, options?: { upsert?: boolean }) =>
|
|
223
224
|
store.updateOneByQuery(query, update, options),
|
|
224
|
-
updateMany: (query: QueryOf<any>, update:
|
|
225
|
+
updateMany: (query: QueryOf<any>, update: DocumentUpdateInput) => store.updateManyByQuery(query, update),
|
|
225
226
|
deleteMany: (query: QueryOf<any>) => store.deleteManyByQuery(query),
|
|
226
227
|
bulkWrite: (
|
|
227
|
-
operations: { updateOne: { filter: QueryOf<any>; update:
|
|
228
|
+
operations: { updateOne: { filter: QueryOf<any>; update: DocumentUpdateInput; upsert?: boolean } }[],
|
|
228
229
|
) => store.bulkWrite(operations),
|
|
229
230
|
listenPre: (type: SaveEventType, listener: (doc: any, type: CRUDEventType) => PromiseOrObject<void>) =>
|
|
230
231
|
schema.pre(type, function (this: any, _next, crudType) {
|
|
@@ -158,6 +158,16 @@ export class SignalResolver {
|
|
|
158
158
|
const serviceName = `${refName}Service`;
|
|
159
159
|
const capitalizedRefName = capitalize(refName);
|
|
160
160
|
|
|
161
|
+
const assertSliceQuery = (query: unknown, key: string) => {
|
|
162
|
+
if (Array.isArray(query))
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Slice "${refName}.${key}" exec returned an array instead of a query descriptor. ` +
|
|
165
|
+
`Return a query from the slice's service (e.g. this.${refName}Service.queryBy...(...)), ` +
|
|
166
|
+
`not an executed list (listBy.../findMany...), which resolves to an array.`,
|
|
167
|
+
);
|
|
168
|
+
return query;
|
|
169
|
+
};
|
|
170
|
+
|
|
161
171
|
class SliceEndpoint extends sliceEndpoint(sliceCls.srv, (builder) => {
|
|
162
172
|
const endpointObj: { [key: string]: EndpointInfo } = {};
|
|
163
173
|
Object.entries(sliceMeta).forEach(([key, sliceInfo]) => {
|
|
@@ -179,7 +189,10 @@ export class SignalResolver {
|
|
|
179
189
|
const limit = Number(requestArgs[argLength + 1] ?? 20);
|
|
180
190
|
const sort = requestArgs[argLength + 2] ?? "latest";
|
|
181
191
|
const internalArgs = requestArgs.slice(argLength + 3);
|
|
182
|
-
const query =
|
|
192
|
+
const query = assertSliceQuery(
|
|
193
|
+
await sliceInfo.execFn?.apply(this, [...args, ...internalArgs, documentQueryHelper]),
|
|
194
|
+
key,
|
|
195
|
+
);
|
|
183
196
|
return (await this[serviceName].__list(query, {
|
|
184
197
|
skip,
|
|
185
198
|
limit,
|
|
@@ -196,7 +209,10 @@ export class SignalResolver {
|
|
|
196
209
|
.exec(async function (this: any, ...requestArgs: any) {
|
|
197
210
|
const args = requestArgs.slice(0, argLength);
|
|
198
211
|
const internalArgs = requestArgs.slice(argLength);
|
|
199
|
-
const query =
|
|
212
|
+
const query = assertSliceQuery(
|
|
213
|
+
await sliceInfo.execFn?.apply(this, [...args, ...internalArgs, documentQueryHelper]),
|
|
214
|
+
key,
|
|
215
|
+
);
|
|
200
216
|
return await this[serviceName].__insight(query);
|
|
201
217
|
});
|
|
202
218
|
});
|
|
@@ -18,9 +18,12 @@ import {
|
|
|
18
18
|
parseRouteModuleKey,
|
|
19
19
|
routeSegmentToTreePath,
|
|
20
20
|
} from "akanjs/common";
|
|
21
|
+
import { createElement } from "react";
|
|
21
22
|
import { validatePageConfig } from "../client/frameConfig";
|
|
22
23
|
import { resolveHeadExport, resolveMetadataHead } from "./metadata";
|
|
23
24
|
|
|
25
|
+
type RouteModuleKindWithOverrides = "page" | "layout" | "overrides";
|
|
26
|
+
|
|
24
27
|
export type PagesContext = Record<string, () => Promise<RouteModule>>;
|
|
25
28
|
|
|
26
29
|
export const defaultPageState: PageState = {
|
|
@@ -197,6 +200,14 @@ export class RouteTreeBuilder {
|
|
|
197
200
|
const targetPath = pathSegments[pathSegments.length - 1];
|
|
198
201
|
if (!targetPath) return;
|
|
199
202
|
|
|
203
|
+
if (parsed.kind === "overrides") {
|
|
204
|
+
targetRouteMap.set(targetPath, {
|
|
205
|
+
...(targetRouteMap.get(targetPath) ?? { path: targetPath, children: new Map<string, Route>() }),
|
|
206
|
+
renderOverrides: this.#makeOverridesRender(filePath, loader),
|
|
207
|
+
} as Route);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
200
211
|
const routeRender = RouteTreeBuilder.#makeRouteRender(filePath, parsed.kind, loader);
|
|
201
212
|
targetRouteMap.set(targetPath, {
|
|
202
213
|
...(targetRouteMap.get(targetPath) ?? { path: targetPath, children: new Map<string, Route>() }),
|
|
@@ -224,8 +235,10 @@ export class RouteTreeBuilder {
|
|
|
224
235
|
const pathSegments = [...parentPaths, ...(currentPathSegment ? [currentPathSegment] : [])];
|
|
225
236
|
const currentRootLayout = isRoot && route.renderLayout ? route.renderLayout : null;
|
|
226
237
|
const currentLayout = !isRoot && route.renderLayout ? route.renderLayout : null;
|
|
238
|
+
|
|
239
|
+
const currentOverrideRenders = route.renderOverrides ? [route.renderOverrides] : [];
|
|
227
240
|
const renderRootLayouts = [...parentRootLayouts, ...(currentRootLayout ? [currentRootLayout] : [])];
|
|
228
|
-
const renderLayouts = [...parentLayouts, ...(currentLayout ? [currentLayout] : [])];
|
|
241
|
+
const renderLayouts = [...parentLayouts, ...currentOverrideRenders, ...(currentLayout ? [currentLayout] : [])];
|
|
229
242
|
if (route.renderLayout) {
|
|
230
243
|
this.#fallbackRoutes.push({
|
|
231
244
|
path: routePath,
|
|
@@ -237,7 +250,10 @@ export class RouteTreeBuilder {
|
|
|
237
250
|
const routeHead = RouteTreeBuilder.#composeHeadResolvers(route.renderLayout?.resolveHead, parentHead);
|
|
238
251
|
const pageRenderRootLayouts =
|
|
239
252
|
route.pageIncludesOwnLayout === false && currentRootLayout ? parentRootLayouts : renderRootLayouts;
|
|
240
|
-
const pageRenderLayouts =
|
|
253
|
+
const pageRenderLayouts =
|
|
254
|
+
route.pageIncludesOwnLayout === false && currentLayout
|
|
255
|
+
? [...parentLayouts, ...currentOverrideRenders]
|
|
256
|
+
: renderLayouts;
|
|
241
257
|
const pageHead = route.pageIncludesOwnLayout === false ? parentHead : routeHead;
|
|
242
258
|
return [
|
|
243
259
|
...(route.renderPage
|
|
@@ -262,7 +278,7 @@ export class RouteTreeBuilder {
|
|
|
262
278
|
];
|
|
263
279
|
}
|
|
264
280
|
|
|
265
|
-
static #makeLazyModule(key: string, kind:
|
|
281
|
+
static #makeLazyModule(key: string, kind: RouteModuleKindWithOverrides, loader: () => Promise<RouteModule>) {
|
|
266
282
|
let cached: RouteModule | null = null;
|
|
267
283
|
let loaded = false;
|
|
268
284
|
RouteTreeBuilder.#moduleCacheStats.moduleCount += 1;
|
|
@@ -285,7 +301,12 @@ export class RouteTreeBuilder {
|
|
|
285
301
|
};
|
|
286
302
|
}
|
|
287
303
|
|
|
288
|
-
static #validateRouteModuleExports(key: string, kind:
|
|
304
|
+
static #validateRouteModuleExports(key: string, kind: RouteModuleKindWithOverrides, mod: RouteModule) {
|
|
305
|
+
if (kind === "overrides") {
|
|
306
|
+
|
|
307
|
+
if (!mod.default) throw new Error(`[route-convention] ${key} generated override wrapper has no default export`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
289
310
|
const parsed = parseRouteModuleKey(key);
|
|
290
311
|
const allowed =
|
|
291
312
|
kind === "page"
|
|
@@ -378,6 +399,18 @@ export class RouteTreeBuilder {
|
|
|
378
399
|
return routeRender;
|
|
379
400
|
}
|
|
380
401
|
|
|
402
|
+
#makeOverridesRender(key: string, loader: () => Promise<RouteModule>): RouteRender {
|
|
403
|
+
const loadModule = RouteTreeBuilder.#makeLazyModule(key, "overrides", loader);
|
|
404
|
+
return {
|
|
405
|
+
isAsync: true,
|
|
406
|
+
render: (async ({ children }: LayoutProps) => {
|
|
407
|
+
const mod = await loadModule();
|
|
408
|
+
if (!mod.default) throw new Error(`[route-convention] ${key} generated override wrapper has no default export`);
|
|
409
|
+
return createElement(mod.default as never, { children } as never);
|
|
410
|
+
}) as RouteRender["render"],
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
381
414
|
static #composeHeadResolvers(...resolvers: (ResolveHead | undefined)[]): ResolveHead | undefined {
|
|
382
415
|
const chain = resolvers.filter((resolver): resolver is ResolveHead => Boolean(resolver));
|
|
383
416
|
if (chain.length === 0) return undefined;
|