akanjs 2.4.1 → 2.4.2-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.
- package/client/csrTypes.ts +7 -0
- package/client/frameConfig.ts +6 -1
- package/client/rscNavigation.ts +9 -0
- package/common/index.ts +5 -0
- package/common/websocketAuth.ts +24 -0
- package/constant/fieldInfo.ts +4 -3
- package/constant/index.ts +2 -0
- package/constant/textFieldPathSet.ts +8 -0
- package/constant/textFieldPaths.ts +59 -0
- package/constant/types.ts +0 -4
- package/constant/via.ts +13 -28
- package/dictionary/dictInfo.ts +4 -0
- package/document/documentQuery.ts +17 -1
- package/document/documentSchema.ts +1 -15
- package/document/filterMeta.ts +4 -2
- package/fetch/client/fetchClient.ts +1 -0
- package/fetch/client/wsClient.ts +28 -1
- package/index.ts +6 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
- package/package.json +1 -1
- package/server/akanServer.ts +5 -4
- package/server/devtools/types.ts +2 -2
- package/server/resolver/database.resolver.ts +5 -6
- package/server/resolver/signal.resolver.ts +23 -0
- package/server/routing/apiRouter.ts +11 -3
- package/server/routing/appWsData.ts +50 -0
- package/server/rscClient.tsx +9 -0
- package/service/predefinedAdaptor/database.adaptor.ts +212 -88
- package/service/predefinedAdaptor/index.ts +1 -0
- package/service/predefinedAdaptor/searchIndex.ts +517 -0
- package/service/predefinedAdaptor/sqlDescriptor.ts +25 -0
- package/signal/signalContext.ts +37 -12
- package/signal/types.ts +2 -1
- package/types/client/csrTypes.d.ts +7 -0
- package/types/client/rscNavigation.d.ts +8 -0
- package/types/common/index.d.ts +1 -0
- package/types/common/websocketAuth.d.ts +19 -0
- package/types/constant/fieldInfo.d.ts +4 -3
- package/types/constant/index.d.ts +2 -0
- package/types/constant/textFieldPathSet.d.ts +8 -0
- package/types/constant/textFieldPaths.d.ts +10 -0
- package/types/constant/types.d.ts +0 -3
- package/types/constant/via.d.ts +4 -24
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/document/documentQuery.d.ts +13 -1
- package/types/document/documentSchema.d.ts +0 -3
- package/types/document/filterMeta.d.ts +2 -1
- package/types/fetch/client/wsClient.d.ts +6 -0
- package/types/index.d.ts +6 -0
- package/types/server/devtools/types.d.ts +2 -2
- package/types/server/resolver/signal.resolver.d.ts +6 -0
- package/types/server/routing/apiRouter.d.ts +3 -4
- package/types/server/routing/appWsData.d.ts +24 -0
- package/types/server/rscClient.d.ts +1 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +46 -13
- package/types/service/predefinedAdaptor/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/searchIndex.d.ts +73 -0
- package/types/service/predefinedAdaptor/sqlDescriptor.d.ts +5 -0
- package/types/signal/signalContext.d.ts +7 -0
- package/types/signal/types.d.ts +5 -1
- package/types/ui/Constant/schemaDoc.d.ts +2 -2
- package/ui/Constant/schemaDoc.ts +8 -2
- package/ui/Model/EditModal.tsx +24 -6
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { dayjs } from "akanjs/base";
|
|
2
|
-
import type
|
|
2
|
+
import { type Logger, websocketAuthContract } from "akanjs/common";
|
|
3
3
|
import type { InjectRegistry } from "akanjs/service";
|
|
4
4
|
import { Exception, type WebsocketReqData } from "akanjs/signal";
|
|
5
5
|
import type { HmrWsData, HmrWsHub } from "../hmr/wsHub";
|
|
6
6
|
import { copyBunRequestFields, type WebProxyRunner } from "../proxy";
|
|
7
7
|
import { SignalResolver } from "../resolver";
|
|
8
8
|
import type { HttpRoutes, SignalRouteOptions, WebsocketRoutes } from "../types";
|
|
9
|
+
import { AppWsData } from "./appWsData";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Minimal render-state view the HMR WS hello message needs.
|
|
@@ -30,7 +31,7 @@ export interface ApiRouteInputs {
|
|
|
30
31
|
routeOptions?: Record<string, SignalRouteOptions>;
|
|
31
32
|
renderEnvRoutes: HttpRoutes;
|
|
32
33
|
/** Upgrades the incoming request into an app-signal WebSocket. */
|
|
33
|
-
upgradeAppWs: (req: Request, data:
|
|
34
|
+
upgradeAppWs: (req: Request, data: AppWsData) => boolean;
|
|
34
35
|
webProxyRunner?: WebProxyRunner | null;
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -85,7 +86,7 @@ export class ApiRouter {
|
|
|
85
86
|
const endpointPaths = new Set([...endpointEntries.map(([path]) => path), ...builtinEntries.map(([path]) => path)]);
|
|
86
87
|
const routeTable = {
|
|
87
88
|
[`${prefix}${websocketPrefix}` as "/api/ws"]: (req) => {
|
|
88
|
-
const upgraded = upgradeAppWs(req,
|
|
89
|
+
const upgraded = upgradeAppWs(req, AppWsData.fromRequest(req));
|
|
89
90
|
if (upgraded) return;
|
|
90
91
|
return new Response("Failed to upgrade to WebSocket", { status: 500 });
|
|
91
92
|
},
|
|
@@ -138,6 +139,13 @@ export class ApiRouter {
|
|
|
138
139
|
if (typeof message === "string") {
|
|
139
140
|
const msg = JSON.parse(message) as WebsocketReqData;
|
|
140
141
|
if (!msg.key) throw new Error("Message key is required");
|
|
142
|
+
if (msg.key === websocketAuthContract.key) {
|
|
143
|
+
|
|
144
|
+
AppWsData.applyCredential(AppWsData.of(ws), websocketAuthContract.readJwt(msg.data));
|
|
145
|
+
const revokedRooms = await SignalResolver.revalidateWsRooms(ws, registry);
|
|
146
|
+
ws.send(JSON.stringify(websocketAuthContract.makeAck(revokedRooms)));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
141
149
|
const wsRoute = wsRoutes[msg.key];
|
|
142
150
|
if (!wsRoute) throw new Error(`WebSocket route "${msg.key}" is not registered`);
|
|
143
151
|
const eventType =
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const CREDENTIAL_HEADERS = ["authorization", "cookie", "user-agent"] as const;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Credential snapshot taken at the websocket handshake and carried on `ws.data` for the life of the
|
|
5
|
+
* socket, so auth middleware and guards can read the caller the same way they read an HTTP request.
|
|
6
|
+
* Only the credential headers are copied — retaining the whole `Request` would pin it for as long
|
|
7
|
+
* as the socket stays open.
|
|
8
|
+
*/
|
|
9
|
+
export class AppWsData {
|
|
10
|
+
static fromRequest(req: Request): AppWsData {
|
|
11
|
+
const headers = new Headers();
|
|
12
|
+
for (const key of CREDENTIAL_HEADERS) {
|
|
13
|
+
const value = req.headers.get(key);
|
|
14
|
+
if (value) headers.set(key, value);
|
|
15
|
+
}
|
|
16
|
+
return new AppWsData(headers);
|
|
17
|
+
}
|
|
18
|
+
static of(ws: Bun.ServerWebSocket<unknown>): AppWsData {
|
|
19
|
+
return ws.data as AppWsData;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Swaps the credential the socket authenticates with. Callers must run this synchronously on the
|
|
23
|
+
* auth frame: frames arrive in order, so a subscribe sent right after the credential must not be
|
|
24
|
+
* able to observe the previous one.
|
|
25
|
+
*/
|
|
26
|
+
static applyCredential(data: AppWsData, jwt: string | null) {
|
|
27
|
+
if (jwt) data.headers.set("authorization", `Bearer ${jwt}`);
|
|
28
|
+
else {
|
|
29
|
+
data.headers.delete("authorization");
|
|
30
|
+
data.cookies.delete("jwt");
|
|
31
|
+
const cookie = [...data.cookies].map(([name, value]) => `${name}=${value}`).join("; ");
|
|
32
|
+
if (cookie) data.headers.set("cookie", cookie);
|
|
33
|
+
else data.headers.delete("cookie");
|
|
34
|
+
}
|
|
35
|
+
data.account = undefined;
|
|
36
|
+
data.resolvedAuthorization = undefined;
|
|
37
|
+
}
|
|
38
|
+
createdAt: number;
|
|
39
|
+
headers: Headers;
|
|
40
|
+
cookies: Bun.CookieMap;
|
|
41
|
+
account?: unknown;
|
|
42
|
+
/** The `authorization` value `account` was resolved from, so each frame need not re-verify it. */
|
|
43
|
+
resolvedAuthorization?: string;
|
|
44
|
+
socketId?: string;
|
|
45
|
+
constructor(headers: Headers) {
|
|
46
|
+
this.createdAt = Date.now();
|
|
47
|
+
this.headers = headers;
|
|
48
|
+
this.cookies = new Bun.CookieMap(headers.get("cookie") ?? "");
|
|
49
|
+
}
|
|
50
|
+
}
|
package/server/rscClient.tsx
CHANGED
|
@@ -47,6 +47,7 @@ declare global {
|
|
|
47
47
|
| undefined;
|
|
48
48
|
var __AKAN_RSC_REFRESH__: ((options?: { buildId?: number }) => Promise<void>) | undefined;
|
|
49
49
|
var __AKAN_RSC_CLEAR_CACHE__: (() => void) | undefined;
|
|
50
|
+
var __AKAN_RSC_IS_FROM_CACHE__: (() => boolean) | undefined;
|
|
50
51
|
var __AKAN_DEV_SYNC_NAVIGATION__: ((href: string, kind: "push" | "replace" | "back" | "pop") => void) | undefined;
|
|
51
52
|
var __AKAN_DEV_SYNC_NAVIGATION_APPLYING__: boolean | undefined;
|
|
52
53
|
var __AKAN_GET_SYNC_ROUTE_HREF__: ((href: string) => string) | undefined;
|
|
@@ -299,8 +300,11 @@ let currentRouterState: AkanRouterStateV1 | null = initialRouterState;
|
|
|
299
300
|
let currentSegmentTree: RscSegmentCacheNode | null = createAkanSegmentCacheTree(initialNode);
|
|
300
301
|
let currentFullNode: RscCacheNode = initialNode;
|
|
301
302
|
let currentCommitKind: "full" | "patch" = "full";
|
|
303
|
+
let currentCommitFromCache = false;
|
|
302
304
|
let navigationSeq = 0;
|
|
303
305
|
|
|
306
|
+
globalThis.__AKAN_RSC_IS_FROM_CACHE__ = () => currentCommitFromCache;
|
|
307
|
+
|
|
304
308
|
function rememberCommittedRouteState(node: RscCacheNode): void {
|
|
305
309
|
rscPatchCache.clear();
|
|
306
310
|
if (!node.routerState) return;
|
|
@@ -378,6 +382,7 @@ function Root(): ReactNode {
|
|
|
378
382
|
maxEntries: MAX_RSC_CACHE_ENTRIES,
|
|
379
383
|
startTransition,
|
|
380
384
|
commitThenable: (node) => {
|
|
385
|
+
currentCommitFromCache = false;
|
|
381
386
|
resetAkanSegmentOutletPatches();
|
|
382
387
|
setThenable(node.thenable);
|
|
383
388
|
},
|
|
@@ -397,6 +402,7 @@ function Root(): ReactNode {
|
|
|
397
402
|
const scrollToTop = options.scrollToTop ?? true;
|
|
398
403
|
try {
|
|
399
404
|
let nextNode = rscCache.get(target);
|
|
405
|
+
const servedFromCache = !!nextNode;
|
|
400
406
|
if (!nextNode) {
|
|
401
407
|
const cachedPatch = rscPatchCache.get(target);
|
|
402
408
|
if (cachedPatch) {
|
|
@@ -425,6 +431,7 @@ function Root(): ReactNode {
|
|
|
425
431
|
bumpScrollToTop: () => setScrollToTopTick((tick) => tick + 1),
|
|
426
432
|
})
|
|
427
433
|
) {
|
|
434
|
+
currentCommitFromCache = true;
|
|
428
435
|
rememberPatchedRouteState(patchResult.tree, patchResult.patchedNode);
|
|
429
436
|
rememberRscPatchCacheNode(rscPatchCache, cachedPatch, MAX_RSC_CACHE_ENTRIES);
|
|
430
437
|
return;
|
|
@@ -449,6 +456,7 @@ function Root(): ReactNode {
|
|
|
449
456
|
bumpScrollToTop: () => setScrollToTopTick((tick) => tick + 1),
|
|
450
457
|
})
|
|
451
458
|
) {
|
|
459
|
+
currentCommitFromCache = false;
|
|
452
460
|
rememberPatchedRouteState(fetched.tree, fetched.patchedNode);
|
|
453
461
|
const patchCacheNode = createRscPatchNavigationCacheNode({
|
|
454
462
|
href: target,
|
|
@@ -493,6 +501,7 @@ function Root(): ReactNode {
|
|
|
493
501
|
maxEntries: MAX_RSC_CACHE_ENTRIES,
|
|
494
502
|
startTransition,
|
|
495
503
|
commitThenable: (node) => {
|
|
504
|
+
currentCommitFromCache = servedFromCache;
|
|
496
505
|
resetAkanSegmentOutletPatches();
|
|
497
506
|
setThenable(node.thenable);
|
|
498
507
|
},
|