akanjs 3.1.0-rc.2 → 3.1.0-rc.20
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/base/baseEnv.ts +5 -2
- package/client/device.ts +1 -1
- package/client/frameConfig.ts +2 -2
- package/client/href.ts +35 -0
- package/client/index.ts +1 -0
- package/client/makePageProto.tsx +3 -2
- package/client/router.ts +6 -4
- package/client/rscNavigation.ts +1 -1
- package/client/transitionTypes.ts +0 -4
- package/client/translator.ts +16 -0
- package/client/types.ts +3 -2
- package/client/viewportMeta.ts +23 -8
- package/client/webManifest.ts +5 -3
- package/common/TrustedProxy.ts +14 -0
- package/common/codeAgentWire.ts +8 -1
- package/common/index.ts +1 -0
- package/common/interpolateTranslation.ts +17 -3
- package/common/isEmail.ts +1 -1
- package/common/localeConfig.ts +10 -4
- package/common/markdownSpans.ts +1 -1
- package/common/restClient.ts +3 -1
- package/common/routeConvention.ts +4 -4
- package/common/trimSlashes.ts +11 -0
- package/common/types.ts +1 -1
- package/constant/fieldInfo.ts +2 -2
- package/constant/fieldPlan.ts +80 -0
- package/constant/index.ts +3 -0
- package/constant/inputParser.ts +148 -0
- package/constant/invalidInputError.ts +13 -0
- package/constant/serialize.ts +36 -20
- package/constant/types.ts +1 -1
- package/constant/via.ts +2 -0
- package/dictionary/base.dictionary.ts +4 -0
- package/dictionary/trans.ts +8 -0
- package/document/database.ts +3 -6
- package/document/documentSchema.ts +1 -1
- package/document/noDocumentError.ts +6 -1
- package/fetch/client/fetchClient.ts +2 -1
- package/fetch/client/sliceInitHandle.ts +2 -3
- package/fetch/fetchType/appliedReturn.type.ts +3 -1
- package/fetch/types.ts +4 -2
- package/package.json +21 -21
- package/server/akanApp.ts +5 -2
- package/server/akanServer.ts +34 -14
- package/server/artifact/routeClientCache.ts +4 -5
- package/server/artifact/routeSeedIndexStore.ts +3 -20
- package/server/artifact/routeSeedMatcher.ts +70 -0
- package/server/console.ts +1 -1
- package/server/di/diLifecycle.ts +16 -7
- package/server/imageOptimizer.ts +1 -1
- package/server/machine/machineAccount.ts +28 -3
- package/server/machine/machineToken.ts +27 -3
- package/server/mcp/McpDispatcher.ts +2 -3
- package/server/mcp/McpExecutionContext.ts +9 -3
- package/server/resolver/CascadeRunner.ts +15 -2
- package/server/resolver/signal.resolver.ts +35 -55
- package/server/routeState.ts +0 -6
- package/server/rscClient.tsx +22 -37
- package/server/rscNavigationState.ts +3 -5
- package/server/rscScrollRestoration.ts +116 -0
- package/server/rscWorker.tsx +12 -19
- package/server/sitemap.ts +4 -4
- package/server/tunnel/TunnelAgent.ts +9 -1
- package/server/webRouter.ts +17 -2
- package/service/agent.service.ts +21 -1
- package/service/endpointCacheKeys.ts +35 -0
- package/service/index.ts +1 -0
- package/service/injectInfo.ts +2 -2
- package/service/predefinedAdaptor/anthropicLlm.ts +37 -17
- package/service/predefinedAdaptor/endpointCacheControl.adaptor.ts +19 -0
- package/service/predefinedAdaptor/index.ts +1 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +7 -0
- package/service/predefinedAdaptor/openaiDialect.ts +53 -17
- package/service/predefinedAdaptor/openaiLlm.ts +1 -1
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +6 -2
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +0 -1
- package/service/predefinedAdaptor/sql/dialect/sqlite.ts +0 -1
- package/service/predefinedAdaptor/storage.adaptor.ts +19 -0
- package/signal/endpointCache.ts +23 -7
- package/signal/endpointErrorContract.ts +37 -0
- package/signal/endpointIdempotency.ts +5 -3
- package/signal/endpointInfo.ts +4 -1
- package/signal/endpointRateLimit.ts +6 -2
- package/signal/exception.ts +4 -1
- package/signal/index.ts +1 -0
- package/signal/mcp/McpDocument.ts +16 -1
- package/signal/mcp/McpUriTemplate.ts +1 -1
- package/signal/middlewareChain.ts +105 -0
- package/signal/openapi/openapi.ts +46 -2
- package/signal/serializer/fetch.serializer.ts +1 -0
- package/signal/signalContext.ts +110 -82
- package/signal/slice.ts +2 -2
- package/signal/trace.ts +8 -2
- package/signal/types.ts +13 -0
- package/store/action.ts +102 -42
- package/store/agent/ScreenImage.ts +166 -0
- package/store/agent/ScreenReader.ts +2 -1
- package/store/agent/StoreSurfaceSource.ts +41 -2
- package/store/agent/index.ts +2 -1
- package/store/databaseStateNames.ts +1 -0
- package/store/formUploadOf.ts +41 -0
- package/store/index.ts +1 -0
- package/store/liveInsert.ts +2 -2
- package/store/state.ts +9 -1
- package/store/stateBuilder.ts +8 -2
- package/store/storeInstance.ts +12 -3
- package/store/withSelector.ts +2 -2
- package/types/client/href.d.ts +11 -0
- package/types/client/index.d.ts +1 -0
- package/types/client/rscNavigation.d.ts +1 -1
- package/types/client/transitionTypes.d.ts +0 -2
- package/types/client/translator.d.ts +2 -0
- package/types/common/codeAgentWire.d.ts +7 -0
- package/types/common/index.d.ts +1 -0
- package/types/common/trimSlashes.d.ts +2 -0
- package/types/common/types.d.ts +1 -1
- package/types/constant/fieldInfo.d.ts +2 -2
- package/types/constant/fieldPlan.d.ts +33 -0
- package/types/constant/index.d.ts +3 -0
- package/types/constant/inputParser.d.ts +11 -0
- package/types/constant/invalidInputError.d.ts +8 -0
- package/types/constant/types.d.ts +1 -1
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +3 -3
- package/types/dictionary/trans.d.ts +3 -0
- package/types/document/database.d.ts +2 -3
- package/types/document/documentSchema.d.ts +1 -1
- package/types/document/noDocumentError.d.ts +6 -1
- package/types/fetch/fetchType/appliedReturn.type.d.ts +3 -1
- package/types/fetch/types.d.ts +2 -2
- package/types/server/akanServer.d.ts +1 -1
- package/types/server/artifact/routeSeedMatcher.d.ts +8 -0
- package/types/server/di/diLifecycle.d.ts +1 -0
- package/types/server/machine/machineAccount.d.ts +1 -0
- package/types/server/machine/machineToken.d.ts +9 -0
- package/types/server/routeState.d.ts +1 -4
- package/types/server/rscNavigationState.d.ts +2 -3
- package/types/server/rscScrollRestoration.d.ts +14 -0
- package/types/server/sitemap.d.ts +1 -1
- package/types/service/agent.service.d.ts +2 -0
- package/types/service/endpointCacheKeys.d.ts +8 -0
- package/types/service/index.d.ts +1 -0
- package/types/service/injectInfo.d.ts +2 -2
- package/types/service/predefinedAdaptor/anthropicLlm.d.ts +9 -4
- package/types/service/predefinedAdaptor/endpointCacheControl.adaptor.d.ts +9 -0
- package/types/service/predefinedAdaptor/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +7 -0
- package/types/service/predefinedAdaptor/openaiDialect.d.ts +20 -5
- package/types/service/predefinedAdaptor/storage.adaptor.d.ts +8 -0
- package/types/signal/endpointCache.d.ts +2 -0
- package/types/signal/endpointErrorContract.d.ts +10 -0
- package/types/signal/endpointIdempotency.d.ts +1 -0
- package/types/signal/endpointRateLimit.d.ts +1 -1
- package/types/signal/exception.d.ts +4 -1
- package/types/signal/index.d.ts +1 -0
- package/types/signal/middlewareChain.d.ts +11 -0
- package/types/signal/signalContext.d.ts +2 -6
- package/types/signal/slice.d.ts +1 -1
- package/types/signal/trace.d.ts +1 -1
- package/types/signal/types.d.ts +12 -0
- package/types/store/action.d.ts +2 -0
- package/types/store/agent/ScreenImage.d.ts +27 -0
- package/types/store/agent/StoreSurfaceSource.d.ts +1 -1
- package/types/store/agent/index.d.ts +2 -1
- package/types/store/databaseStateNames.d.ts +1 -0
- package/types/store/formUploadOf.d.ts +9 -0
- package/types/store/index.d.ts +1 -0
- package/types/store/state.d.ts +7 -1
- package/types/store/stateBuilder.d.ts +2 -1
- package/types/store/storeInstance.d.ts +2 -2
- package/types/store/withSelector.d.ts +1 -1
- package/types/ui/Agent/Composer.d.ts +2 -0
- package/types/ui/Agent/Menu.d.ts +2 -0
- package/types/ui/CsrImage.d.ts +2 -0
- package/types/ui/Data/CardList.d.ts +1 -1
- package/types/ui/Data/Dashboard.d.ts +1 -1
- package/types/ui/Data/Insight.d.ts +1 -1
- package/types/ui/Data/Item.d.ts +2 -2
- package/types/ui/Data/ListContainer.d.ts +1 -1
- package/types/ui/Data/Pagination.d.ts +1 -1
- package/types/ui/Data/TableList.d.ts +1 -1
- package/types/ui/Field/Relation.d.ts +13 -10
- package/types/ui/Field/index.d.ts +4 -4
- package/types/ui/Field/useRelationOptions.d.ts +3 -1
- package/types/ui/Field/useRelationWindow.d.ts +14 -0
- package/types/ui/Image.d.ts +3 -1
- package/types/ui/Load/View.d.ts +1 -1
- package/types/ui/Pagination.d.ts +1 -1
- package/types/ui/Signal/Arg.d.ts +1 -1
- package/types/ui/System/CSR.d.ts +1 -1
- package/types/ui/System/Client.d.ts +1 -1
- package/types/ui/System/SSR.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/core/src/plugin.d.ts +3 -3
- package/types/vendor/@akanjs/native/packages/desktop/src/page-veto.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/auth-session/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/auth-session/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/autostart/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/autostart/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/badge/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/badge/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/badge/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/biometric/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/camera/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/camera/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/camera/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/clipboard/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/clipboard/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/clipboard/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/contacts/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/device/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/device/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/device/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dialog/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dialog/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dialog/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dock/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dock/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/file-picker/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/file-picker/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/filesystem/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/filesystem/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/haptics/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/haptics/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/http/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/http/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/keep-awake/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/keep-awake/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/keep-awake/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/opener/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/opener/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/opener/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/preferences/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/preferences/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/preferences/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/secure-storage/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/secure-storage/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/share/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/share/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/single-instance/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/splash-screen/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/splash-screen/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/sqlite/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/toast/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/toast/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/window-state/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/window-state/src/index.d.ts +1 -1
- package/types/vendor/use-agentic/Compaction.d.ts +3 -1
- package/types/vendor/use-agentic/Reference.d.ts +9 -1
- package/types/vendor/use-agentic/ToolOutput.d.ts +6 -1
- package/types/vendor/use-agentic/ToolReturn.d.ts +18 -0
- package/types/vendor/use-agentic/Transcript.d.ts +7 -0
- package/types/vendor/use-agentic/index.d.ts +1 -0
- package/types/vendor/use-agentic/types.d.ts +6 -0
- package/types/webkit/useDebounce.d.ts +1 -1
- package/ui/Agent/Chat.tsx +1 -0
- package/ui/Agent/Composer.tsx +2 -0
- package/ui/Agent/Menu.tsx +5 -1
- package/ui/Agent/agentSessionOf.ts +15 -8
- package/ui/Agent/mentionDraft.ts +2 -3
- package/ui/Agent/useReferenceMenu.ts +1 -0
- package/ui/Agent/useSlashMenu.ts +1 -0
- package/ui/Clipboard.tsx +1 -0
- package/ui/CsrImage.tsx +3 -1
- package/ui/Data/CardList.tsx +1 -1
- package/ui/Data/Dashboard.tsx +1 -1
- package/ui/Data/Insight.tsx +1 -1
- package/ui/Data/Item.tsx +5 -2
- package/ui/Data/ListContainer.tsx +2 -2
- package/ui/Data/Pagination.tsx +1 -1
- package/ui/Data/TableList.tsx +1 -1
- package/ui/DraggableList.tsx +1 -0
- package/ui/Field/Collection.tsx +4 -0
- package/ui/Field/Relation.tsx +33 -14
- package/ui/Field/useRelationOptions.ts +33 -5
- package/ui/Field/useRelationWindow.ts +50 -0
- package/ui/Image.tsx +4 -4
- package/ui/Input.tsx +1 -0
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Link/Anchor.tsx +7 -5
- package/ui/Load/Units.tsx +17 -7
- package/ui/Load/View.tsx +2 -2
- package/ui/Model/Edit.tsx +1 -1
- package/ui/Model/EditModal.tsx +11 -5
- package/ui/Model/New.tsx +1 -1
- package/ui/Model/SureToRemove.tsx +1 -0
- package/ui/Model/ViewEditModal.tsx +17 -5
- package/ui/Model/ViewModal.tsx +2 -2
- package/ui/ObjectId.tsx +5 -1
- package/ui/Pagination.tsx +1 -1
- package/ui/Signal/Arg.tsx +1 -3
- package/ui/Signal/Doc.tsx +5 -1
- package/ui/System/CSR.tsx +1 -1
- package/ui/System/Client.tsx +1 -1
- package/ui/System/Reconnect.tsx +1 -1
- package/ui/System/SSR.tsx +3 -36
- package/ui/ToggleSelect.tsx +2 -0
- package/ui/styles.css +10 -103
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeBridge.swift +3 -2
- package/vendor/@akanjs/native/packages/cli/src/lib/addons.ts +4 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/apk.ts +2 -2
- package/vendor/@akanjs/native/packages/cli/src/lib/codegen.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/csp.ts +4 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/googleservices.ts +7 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/hmr.ts +8 -5
- package/vendor/@akanjs/native/packages/cli/src/lib/html.ts +18 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/manifestmerge.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/plist.ts +2 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/toolchains.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +10 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/xml.ts +4 -3
- package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +3 -3
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +3 -3
- package/vendor/@akanjs/native/packages/core/src/live.ts +1 -0
- package/vendor/@akanjs/native/packages/core/src/plugin.ts +5 -3
- package/vendor/@akanjs/native/packages/desktop/src/host.ts +13 -5
- package/vendor/@akanjs/native/packages/desktop/src/lifecycle.ts +1 -0
- package/vendor/@akanjs/native/packages/desktop/src/page-veto.ts +1 -1
- package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +4 -1
- package/vendor/@akanjs/native/plugins/camera/src/web.ts +3 -3
- package/vendor/@akanjs/native/plugins/file-picker/src/index.ts +4 -1
- package/vendor/@akanjs/native/plugins/local-notifications/src/web.ts +1 -1
- package/vendor/@akanjs/native/plugins/secure-storage/src/desktop.ts +1 -1
- package/vendor/@akanjs/native/plugins/share/src/options.ts +1 -1
- package/vendor/@akanjs/native/plugins/single-instance/src/desktop.ts +3 -2
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +4 -1
- package/vendor/@akanjs/native/plugins/window/src/index.ts +2 -2
- package/vendor/use-agentic/Compaction.ts +38 -7
- package/vendor/use-agentic/Reference.ts +16 -9
- package/vendor/use-agentic/ToolOutput.ts +25 -3
- package/vendor/use-agentic/ToolReturn.ts +32 -0
- package/vendor/use-agentic/ToolRunner.ts +15 -13
- package/vendor/use-agentic/Transcript.ts +38 -2
- package/vendor/use-agentic/WIRE.md +20 -0
- package/vendor/use-agentic/index.ts +1 -0
- package/vendor/use-agentic/types.ts +6 -0
- package/webkit/McpAppAgent.ts +12 -2
- package/webkit/RenderLayer.tsx +23 -4
- package/webkit/useDebounce.ts +23 -14
- package/client/viewTransition.ts +0 -10
- package/types/client/viewTransition.d.ts +0 -3
package/base/baseEnv.ts
CHANGED
|
@@ -81,8 +81,11 @@ const globalWithPrefix = globalThis as typeof globalThis & { __AKAN_PREFIX__?: R
|
|
|
81
81
|
export const normalizeRoutePrefix = (value: string | undefined | null): string | undefined => {
|
|
82
82
|
const trimmed = value?.trim();
|
|
83
83
|
if (!trimmed) return undefined;
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
let start = 0;
|
|
85
|
+
let end = trimmed.length;
|
|
86
|
+
while (start < end && trimmed[start] === "/") start += 1;
|
|
87
|
+
while (end > start && trimmed[end - 1] === "/") end -= 1;
|
|
88
|
+
return start === end ? undefined : `/${trimmed.slice(start, end)}`;
|
|
86
89
|
};
|
|
87
90
|
|
|
88
91
|
/**
|
package/client/device.ts
CHANGED
|
@@ -11,7 +11,7 @@ type DebugPayload = Record<string, unknown>;
|
|
|
11
11
|
|
|
12
12
|
const globalWithProcess = globalThis as typeof globalThis & { process?: ProcessEnvLike };
|
|
13
13
|
|
|
14
|
-
const debugSessionId =
|
|
14
|
+
const debugSessionId = crypto.getRandomValues(new Uint32Array(1))[0].toString(36);
|
|
15
15
|
let debugSeq = 0;
|
|
16
16
|
|
|
17
17
|
const FRAME_TRACE_LIMIT = 2000;
|
package/client/frameConfig.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRecord } from "akanjs/common";
|
|
1
|
+
import { isRecord, trimSlashes } from "akanjs/common";
|
|
2
2
|
import type { PageConfig, PageSafeAreaConfig, PageState, ResponsiveInset, SsrRenderMode } from "./csrTypes";
|
|
3
3
|
import { transitionTypes } from "./transitionTypes";
|
|
4
4
|
|
|
@@ -246,7 +246,7 @@ const getPlatformFrameProfile = (platform: DevicePlatform): PageConfig => {
|
|
|
246
246
|
};
|
|
247
247
|
|
|
248
248
|
const getRouteRoleFrameDefaults = (path: string, platform: DevicePlatform, basePath?: string): PageConfig => {
|
|
249
|
-
const normalizedBasePath = basePath
|
|
249
|
+
const normalizedBasePath = trimSlashes(basePath ?? "");
|
|
250
250
|
const depth = path
|
|
251
251
|
.split("/")
|
|
252
252
|
.filter(Boolean)
|
package/client/href.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class Href {
|
|
2
|
+
static readonly #blocked = new Set(["javascript:", "vbscript:", "data:"]);
|
|
3
|
+
|
|
4
|
+
static #parse(href: string, base?: string): URL | null {
|
|
5
|
+
try {
|
|
6
|
+
return new URL(href, base);
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** External is what the browser takes off-site: any absolute URL with a scheme, and `//`/`\\` network-path
|
|
13
|
+
* forms that name a host. javascript:, vbscript: and data: never count. */
|
|
14
|
+
static isExternal(href: string): boolean {
|
|
15
|
+
|
|
16
|
+
const absolute = Href.#parse(href);
|
|
17
|
+
if (absolute) return !Href.#blocked.has(absolute.protocol);
|
|
18
|
+
|
|
19
|
+
const value = href.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+/, "");
|
|
20
|
+
return /^[\\/]{2}/.test(value) && !!Href.#parse(value, "https://localhost")?.hostname;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** An explicit http(s) scheme in any case; `//host` and `httpbin/x` do not qualify. */
|
|
24
|
+
static hasWebScheme(href: string): boolean {
|
|
25
|
+
return /^https?:/i.test(href);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Canonical absolute URL for an external href: follows the browser on an http(s) document base; on a native
|
|
29
|
+
* `app://` base an absolute URL resolves as written and a network path resolves against https:. */
|
|
30
|
+
static toExternalUrl(href: string, base: string): string {
|
|
31
|
+
const baseUrl = Href.#parse(base);
|
|
32
|
+
if (baseUrl && /^https?:$/.test(baseUrl.protocol)) return Href.#parse(href, baseUrl.href)?.href ?? href;
|
|
33
|
+
return (Href.#parse(href) ?? Href.#parse(href, "https://localhost"))?.href ?? href;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/client/index.ts
CHANGED
package/client/makePageProto.tsx
CHANGED
|
@@ -28,11 +28,12 @@ const getPageInfo = (): { locale: string; path: string } => {
|
|
|
28
28
|
const activePath = Translator.getActivePath();
|
|
29
29
|
if (activePath) return { locale: activeLocale ?? defaultLocale, path: activePath };
|
|
30
30
|
if (getEnv().side !== "server") {
|
|
31
|
-
const
|
|
31
|
+
const pathname = Translator.getRenderPath() ?? csrLocation.pathname;
|
|
32
|
+
const [, firstSegment = "", ...rest] = pathname.split("/");
|
|
32
33
|
const hasLocalePrefix = localeSet.has(firstSegment);
|
|
33
34
|
|
|
34
35
|
const locale = activeLocale ?? (hasLocalePrefix ? firstSegment : defaultLocale);
|
|
35
|
-
return { locale, path:
|
|
36
|
+
return { locale, path: hasLocalePrefix ? `/${rest.join("/")}` : pathname };
|
|
36
37
|
}
|
|
37
38
|
const h = untrackedHeaders();
|
|
38
39
|
|
package/client/router.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getEnv } from "akanjs/base";
|
|
2
|
-
import { getBasePathFromPathname, Logger, parseAkanI18nEnv, parseBasePaths } from "akanjs/common";
|
|
2
|
+
import { getBasePathFromPathname, Logger, parseAkanI18nEnv, parseBasePaths, trimSlashes } from "akanjs/common";
|
|
3
3
|
import { csrLocation } from "./csrLocation";
|
|
4
|
+
import { Href } from "./href";
|
|
4
5
|
import { isMcpView } from "./mcpView";
|
|
5
6
|
|
|
6
7
|
export interface RouteOptions {
|
|
@@ -99,7 +100,7 @@ const normalizeRoutePath = (path: string | undefined) => {
|
|
|
99
100
|
if (!normalized) return undefined;
|
|
100
101
|
const [pathWithoutHash, hash = ""] = normalized.split("#");
|
|
101
102
|
const [pathname, search = ""] = pathWithoutHash.split("?");
|
|
102
|
-
const routePath = `/${pathname
|
|
103
|
+
const routePath = `/${trimSlashes(pathname)}`;
|
|
103
104
|
return `${routePath === "/" ? "/" : routePath}${search ? `?${search}` : ""}${hash ? `#${hash}` : ""}`;
|
|
104
105
|
};
|
|
105
106
|
|
|
@@ -158,7 +159,8 @@ export const getPathInfo = (requestUrl: string, lang: string, prefix: string) =>
|
|
|
158
159
|
? pathWithSubRoute.slice(prefixLength)
|
|
159
160
|
: pathWithSubRoute;
|
|
160
161
|
const subRoute = prefix ? `/${prefix}` : "";
|
|
161
|
-
|
|
162
|
+
|
|
163
|
+
const pathname = Href.hasWebScheme(path) ? path : path === "/" ? `/${lang}${subRoute}` : `/${lang}${subRoute}${path}`;
|
|
162
164
|
const href = `${pathname}${search ? `?${search}` : ""}${hash ? `#${hash}` : ""}`;
|
|
163
165
|
return { path, pathname, hash, search, href };
|
|
164
166
|
};
|
|
@@ -453,7 +455,7 @@ class Router {
|
|
|
453
455
|
const h = requestHeaders();
|
|
454
456
|
const req = getRequest();
|
|
455
457
|
const reqPathname = req ? new URL(req.url).pathname : "";
|
|
456
|
-
const langFromPath = reqPathname.split("/").
|
|
458
|
+
const langFromPath = reqPathname.split("/").find(Boolean);
|
|
457
459
|
const lang = (h.get("x-locale") ?? langFromPath ?? this.#lang) as string;
|
|
458
460
|
const basePath = getServerBasePath(reqPathname, lang, h.get("x-base-path") ?? undefined, this.#prefix);
|
|
459
461
|
const { pathname, href: fullHref } = getPathInfo(href, lang, shouldExposeBasePath() ? basePath : "");
|
package/client/rscNavigation.ts
CHANGED
|
@@ -5,7 +5,7 @@ declare global {
|
|
|
5
5
|
var __AKAN_RSC_NAVIGATE__:
|
|
6
6
|
| ((
|
|
7
7
|
href: string,
|
|
8
|
-
options?: { replace?: boolean; scrollToTop?: boolean;
|
|
8
|
+
options?: { replace?: boolean; scrollToTop?: boolean; scrollY?: number; shallow?: boolean },
|
|
9
9
|
) => Promise<void>)
|
|
10
10
|
| undefined;
|
|
11
11
|
var __AKAN_DEV_SYNC_NAVIGATION__: ((href: string, kind: "push" | "replace" | "back" | "pop") => void) | undefined;
|
|
@@ -1,6 +1,2 @@
|
|
|
1
1
|
export const transitionTypes = ["none", "fade", "bottomUp", "stack", "scaleOut"] as const;
|
|
2
2
|
export type TransitionType = (typeof transitionTypes)[number];
|
|
3
|
-
|
|
4
|
-
/** The class `ui/styles.css` animates a page by when a navigation plays `transition` forward (push) or back (pop). */
|
|
5
|
-
export const viewTransitionClassOf = (transition: Exclude<TransitionType, "none">, direction: "push" | "pop") =>
|
|
6
|
-
`akan-${transition}-${direction}` as const;
|
package/client/translator.ts
CHANGED
|
@@ -18,6 +18,8 @@ interface TranslatorState {
|
|
|
18
18
|
activeLocale?: string;
|
|
19
19
|
|
|
20
20
|
activePath?: string;
|
|
21
|
+
|
|
22
|
+
renderPath?: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const TRANSLATOR_STATE_KEY = "__AKAN_TRANSLATOR_STATE__";
|
|
@@ -57,6 +59,20 @@ export class Translator {
|
|
|
57
59
|
static getActivePath(): string | undefined {
|
|
58
60
|
return getTranslatorState().activePath;
|
|
59
61
|
}
|
|
62
|
+
static renderAt<T>(path: string | undefined, render: () => T): T {
|
|
63
|
+
if (!path) return render();
|
|
64
|
+
const state = getTranslatorState();
|
|
65
|
+
const outer = state.renderPath;
|
|
66
|
+
state.renderPath = path;
|
|
67
|
+
try {
|
|
68
|
+
return render();
|
|
69
|
+
} finally {
|
|
70
|
+
state.renderPath = outer;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
static getRenderPath(): string | undefined {
|
|
74
|
+
return getTranslatorState().renderPath;
|
|
75
|
+
}
|
|
60
76
|
static markHydrated() {
|
|
61
77
|
delete getTranslatorState().activePath;
|
|
62
78
|
}
|
package/client/types.ts
CHANGED
|
@@ -102,8 +102,9 @@ const slugFontPart = (value: string) =>
|
|
|
102
102
|
value
|
|
103
103
|
.trim()
|
|
104
104
|
.toLowerCase()
|
|
105
|
-
.
|
|
106
|
-
.
|
|
105
|
+
.split(/[^a-z0-9]+/)
|
|
106
|
+
.filter(Boolean)
|
|
107
|
+
.join("-") || "font";
|
|
107
108
|
|
|
108
109
|
export interface ModelsProps<M extends { id: string }> {
|
|
109
110
|
className?: string;
|
package/client/viewportMeta.ts
CHANGED
|
@@ -7,6 +7,9 @@ export interface ThemeColorTag {
|
|
|
7
7
|
|
|
8
8
|
/** Turns a root layout's `.viewport()` into the `viewport` and `theme-color` meta tags every document shell writes. */
|
|
9
9
|
export class ViewportMeta {
|
|
10
|
+
static readonly #offMedia = "not all";
|
|
11
|
+
static readonly #ownAttr = "data-akan-theme-color";
|
|
12
|
+
|
|
10
13
|
static content(viewport: RouteViewport | undefined, { native = false }: { native?: boolean } = {}): string {
|
|
11
14
|
const parts = ["width=device-width", "initial-scale=1"];
|
|
12
15
|
if (viewport?.fitCover ?? native) parts.push("viewport-fit=cover");
|
|
@@ -39,18 +42,30 @@ export class ViewportMeta {
|
|
|
39
42
|
const tags = ViewportMeta.themeColors(viewport, theme);
|
|
40
43
|
if (!tags.length) return;
|
|
41
44
|
const existing = [...document.head.querySelectorAll<HTMLMetaElement>('meta[name="theme-color"]')];
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const live = existing.filter((meta) => meta.getAttribute("media") !== ViewportMeta.#offMedia);
|
|
46
|
+
const isCurrent =
|
|
47
|
+
live.length === tags.length &&
|
|
48
|
+
tags.every(
|
|
49
|
+
(tag, idx) => live[idx]?.content === tag.content && live[idx]?.getAttribute("media") === (tag.media ?? null),
|
|
50
|
+
);
|
|
51
|
+
if (isCurrent) return;
|
|
52
|
+
|
|
53
|
+
for (const meta of existing) {
|
|
54
|
+
if (meta.hasAttribute(ViewportMeta.#ownAttr)) meta.remove();
|
|
55
|
+
else meta.setAttribute("media", ViewportMeta.#offMedia);
|
|
56
|
+
}
|
|
57
|
+
for (const tag of tags) {
|
|
58
|
+
const meta = document.createElement("meta");
|
|
59
|
+
meta.name = "theme-color";
|
|
46
60
|
meta.content = tag.content;
|
|
47
61
|
if (tag.media) meta.setAttribute("media", tag.media);
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
meta.setAttribute(ViewportMeta.#ownAttr, "");
|
|
63
|
+
document.head.appendChild(meta);
|
|
64
|
+
}
|
|
50
65
|
}
|
|
51
66
|
|
|
52
|
-
static #ensureMeta(name: string
|
|
53
|
-
const found =
|
|
67
|
+
static #ensureMeta(name: string): HTMLMetaElement {
|
|
68
|
+
const found = document.head.querySelector<HTMLMetaElement>(`meta[name="${name}"]`);
|
|
54
69
|
if (found) return found;
|
|
55
70
|
const meta = document.createElement("meta");
|
|
56
71
|
meta.name = name;
|
package/client/webManifest.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { trimSlashes } from "akanjs/common";
|
|
2
|
+
|
|
1
3
|
/** Where an SSR document links its root layout's `.manifest()`; the server answers it from that layout. */
|
|
2
4
|
export const webManifestHref = (basePath?: string | null) =>
|
|
3
5
|
basePath ? `/${basePath}/manifest.webmanifest` : "/manifest.webmanifest";
|
|
4
6
|
|
|
5
|
-
const
|
|
7
|
+
const trimBasePath = (value: string) => trimSlashes(value.trim());
|
|
6
8
|
|
|
7
9
|
/** The basePath a `/manifest.webmanifest` request asks for: by a visible prefix, else by the request's basePath. */
|
|
8
10
|
export const getWebManifestBasePath = (
|
|
@@ -12,9 +14,9 @@ export const getWebManifestBasePath = (
|
|
|
12
14
|
): string | null | undefined => {
|
|
13
15
|
const segments = pathname.trim().split("/").filter(Boolean);
|
|
14
16
|
if (segments.at(-1) !== "manifest.webmanifest" || segments.length > 2) return undefined;
|
|
15
|
-
const known = basePaths.map(
|
|
17
|
+
const known = basePaths.map(trimBasePath);
|
|
16
18
|
if (segments.length === 2) return known.includes(segments[0] ?? "") ? (segments[0] ?? null) : undefined;
|
|
17
|
-
const fromHeader =
|
|
19
|
+
const fromHeader = trimBasePath(headerBasePath ?? "");
|
|
18
20
|
return known.includes(fromHeader) ? fromHeader : null;
|
|
19
21
|
};
|
|
20
22
|
|
package/common/TrustedProxy.ts
CHANGED
|
@@ -3,8 +3,12 @@ import { clientAddressFromHeaders, normalizeIpAddress } from "./clientAddress";
|
|
|
3
3
|
export class TrustedProxy {
|
|
4
4
|
static #cidrs: { bytes: Uint8Array; bits: number }[] | null = null;
|
|
5
5
|
static #trustAll: boolean | null = null;
|
|
6
|
+
|
|
7
|
+
static readonly #verdicts = new Map<string, boolean>();
|
|
8
|
+
static readonly #verdictLimit = 1024;
|
|
6
9
|
|
|
7
10
|
static configure(list: string | null | undefined) {
|
|
11
|
+
TrustedProxy.#verdicts.clear();
|
|
8
12
|
TrustedProxy.#trustAll = list?.trim() === "*";
|
|
9
13
|
TrustedProxy.#cidrs = (list ?? "")
|
|
10
14
|
.split(",")
|
|
@@ -19,6 +23,7 @@ export class TrustedProxy {
|
|
|
19
23
|
static reset() {
|
|
20
24
|
TrustedProxy.#cidrs = null;
|
|
21
25
|
TrustedProxy.#trustAll = null;
|
|
26
|
+
TrustedProxy.#verdicts.clear();
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
static #load() {
|
|
@@ -29,6 +34,15 @@ export class TrustedProxy {
|
|
|
29
34
|
if (!address) return false;
|
|
30
35
|
TrustedProxy.#load();
|
|
31
36
|
if (TrustedProxy.#trustAll) return true;
|
|
37
|
+
const known = TrustedProxy.#verdicts.get(address);
|
|
38
|
+
if (known !== undefined) return known;
|
|
39
|
+
const verdict = TrustedProxy.#judge(address);
|
|
40
|
+
if (TrustedProxy.#verdicts.size >= TrustedProxy.#verdictLimit) TrustedProxy.#verdicts.clear();
|
|
41
|
+
TrustedProxy.#verdicts.set(address, verdict);
|
|
42
|
+
return verdict;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static #judge(address: string): boolean {
|
|
32
46
|
const bytes = TrustedProxy.#toBytes(normalizeIpAddress(address));
|
|
33
47
|
if (!bytes) return false;
|
|
34
48
|
if (TrustedProxy.#isLocalRange(bytes)) return true;
|
package/common/codeAgentWire.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface CodeAgentSessionInfo {
|
|
|
18
18
|
contextTokens: number | undefined;
|
|
19
19
|
/** Absent when the model does no reasoning at all, which is not reasoning turned `off`. */
|
|
20
20
|
effort: CodeAgentEffort | undefined;
|
|
21
|
+
/** Percent of the window that starts a compaction; absent leaves the engine's own, later threshold. */
|
|
22
|
+
autoCompactAt: number | undefined;
|
|
21
23
|
/** Absent until the session has been asked something. */
|
|
22
24
|
name: string | undefined;
|
|
23
25
|
/** `await` keeps a question inside its turn; `suspend` ends the turn (`awaiting`) and the answer opens a new one. */
|
|
@@ -186,6 +188,8 @@ export type CodeAgentCommand =
|
|
|
186
188
|
| { type: "abort" }
|
|
187
189
|
| { type: "compact"; instructions?: string }
|
|
188
190
|
| { type: "set_model"; provider: string; modelId: string }
|
|
191
|
+
/** `percent` in steps of 10; absent, or 100, restores the default. */
|
|
192
|
+
| { type: "set_auto_compact"; percent?: number }
|
|
189
193
|
| { type: "fork"; entryId: string }
|
|
190
194
|
| { type: "new_session" }
|
|
191
195
|
/** Current state, plus every frame after `sinceSeq` when one is given. */
|
|
@@ -231,8 +235,11 @@ export const codeAgentEventLabel = (event: CodeAgentEventBody): string => {
|
|
|
231
235
|
`session ${event.info.sessionId}`,
|
|
232
236
|
event.info.model?.name ?? "no model",
|
|
233
237
|
event.info.contextTokens ? `${Math.round(event.info.contextTokens / 1000)}k ctx` : "unknown ctx",
|
|
238
|
+
event.info.autoCompactAt ? `compacts at ${event.info.autoCompactAt}%` : "",
|
|
234
239
|
event.info.profile,
|
|
235
|
-
]
|
|
240
|
+
]
|
|
241
|
+
.filter((part) => !!part)
|
|
242
|
+
.join(" · ");
|
|
236
243
|
case "turn_start":
|
|
237
244
|
return "turn start";
|
|
238
245
|
case "turn_end":
|
package/common/index.ts
CHANGED
|
@@ -199,6 +199,7 @@ export { sleep } from "./sleep";
|
|
|
199
199
|
export { getBasePathFromPathname, parseBasePaths, parseSubRouteHosts, resolveSubRouteHosts } from "./subRoute";
|
|
200
200
|
export { TrustedProxy } from "./TrustedProxy";
|
|
201
201
|
export { toError } from "./toError";
|
|
202
|
+
export { trimSlashes, trimTrailingSlashes } from "./trimSlashes";
|
|
202
203
|
export {
|
|
203
204
|
type TunnelAgentIdentity,
|
|
204
205
|
type TunnelAttachedFrame,
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
/** A `{name}` placeholder whose value is absent is left as written, never replaced by "undefined". */
|
|
2
2
|
export const interpolateTranslation = (message: string, data: Record<string, unknown> | undefined) => {
|
|
3
3
|
if (!data) return message;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
let result = "";
|
|
6
|
+
let copied = 0;
|
|
7
|
+
let open = message.indexOf("{");
|
|
8
|
+
while (open !== -1) {
|
|
9
|
+
const close = message.indexOf("}", open + 1);
|
|
10
|
+
if (close === -1) break;
|
|
11
|
+
if (close === open + 1) {
|
|
12
|
+
open = message.indexOf("{", close);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const value = data[message.slice(open + 1, close)];
|
|
16
|
+
result += message.slice(copied, open) + (value === undefined ? message.slice(open, close + 1) : String(value));
|
|
17
|
+
copied = close + 1;
|
|
18
|
+
open = message.indexOf("{", copied);
|
|
19
|
+
}
|
|
20
|
+
return result + message.slice(copied);
|
|
7
21
|
};
|
package/common/isEmail.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const isEmail = (email?: string | null) => !!email && /^\w+([.-]
|
|
1
|
+
export const isEmail = (email?: string | null) => !!email && /^\w+(?:[.-]\w+)*@\w+(?:[.-]\w+)*\.\w{2,8}$/.test(email);
|
package/common/localeConfig.ts
CHANGED
|
@@ -28,14 +28,20 @@ export function resolveAkanI18nConfig(input: AkanI18nConfigInput = {}): AkanI18n
|
|
|
28
28
|
return { defaultLocale, locales };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
let lastParsed: { defaultLocale?: string; locales?: string; config: AkanI18nConfig } | null = null;
|
|
32
|
+
|
|
31
33
|
export function parseAkanI18nEnv(
|
|
32
34
|
env: Record<string, string | undefined> = {
|
|
33
35
|
AKAN_PUBLIC_DEFAULT_LOCALE: typeof process !== "undefined" ? process.env.AKAN_PUBLIC_DEFAULT_LOCALE : undefined,
|
|
34
36
|
AKAN_PUBLIC_LOCALES: typeof process !== "undefined" ? process.env.AKAN_PUBLIC_LOCALES : undefined,
|
|
35
37
|
},
|
|
36
38
|
): AkanI18nConfig {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const defaultLocale = env.AKAN_PUBLIC_DEFAULT_LOCALE;
|
|
40
|
+
const locales = env.AKAN_PUBLIC_LOCALES;
|
|
41
|
+
if (lastParsed && lastParsed.defaultLocale === defaultLocale && lastParsed.locales === locales)
|
|
42
|
+
return lastParsed.config;
|
|
43
|
+
const resolved = resolveAkanI18nConfig({ defaultLocale, locales: locales?.split(",") });
|
|
44
|
+
const config = Object.freeze({ ...resolved, locales: Object.freeze(resolved.locales) as unknown as string[] });
|
|
45
|
+
lastParsed = { defaultLocale, locales, config };
|
|
46
|
+
return config;
|
|
41
47
|
}
|
package/common/markdownSpans.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type MarkdownSpan =
|
|
|
9
9
|
| { kind: "plain"; text: string };
|
|
10
10
|
|
|
11
11
|
const inline =
|
|
12
|
-
/(!?)\[([
|
|
12
|
+
/(!?)\[([^[\]]*)\]\(((?:[^\s()]|\([^\s()]*\))+)\)|`([^`]+)`|\*\*([\s\S]+?)\*\*|\*([^*\n]+?)\*|~~([\s\S]+?)~~/g;
|
|
13
13
|
|
|
14
14
|
export class MarkdownSpans {
|
|
15
15
|
static of(text: string): MarkdownSpan[] {
|
package/common/restClient.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { trimTrailingSlashes } from "./trimSlashes";
|
|
2
|
+
|
|
1
3
|
export interface RestClientOptions {
|
|
2
4
|
baseUrl?: string;
|
|
3
5
|
headers?: HeadersInit;
|
|
@@ -16,7 +18,7 @@ export class RestClient {
|
|
|
16
18
|
|
|
17
19
|
constructor(options: string | RestClientOptions = {}) {
|
|
18
20
|
const normalized = typeof options === "string" ? { baseUrl: options } : options;
|
|
19
|
-
this.baseUrl = normalized.baseUrl
|
|
21
|
+
this.baseUrl = trimTrailingSlashes(normalized.baseUrl ?? "");
|
|
20
22
|
this.headers = new Headers(normalized.headers);
|
|
21
23
|
this.timeout = normalized.timeout;
|
|
22
24
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { trimSlashes, trimTrailingSlashes } from "./trimSlashes";
|
|
2
|
+
|
|
1
3
|
const ROUTE_SOURCE_RE = /^\.\/(.+)\.(tsx|ts|jsx|js)$/;
|
|
2
4
|
const SOURCE_EXT_RE = /\.(tsx|ts|jsx|js)$/;
|
|
3
5
|
const RESERVED_ROUTE_FILES = new Set(["_layout", "_index", "_overrides"]);
|
|
@@ -97,9 +99,7 @@ export function validateSubRoutePageKey(
|
|
|
97
99
|
basePaths: Iterable<string>,
|
|
98
100
|
options: ValidateSubRoutePageKeyOptions = {},
|
|
99
101
|
): void {
|
|
100
|
-
const allowedBasePaths = [
|
|
101
|
-
...new Set([...basePaths].map((basePath) => basePath.trim().replace(/^\/+|\/+$/g, "")).filter(Boolean)),
|
|
102
|
-
];
|
|
102
|
+
const allowedBasePaths = [...new Set([...basePaths].map((basePath) => trimSlashes(basePath.trim())).filter(Boolean))];
|
|
103
103
|
if (allowedBasePaths.length === 0) return;
|
|
104
104
|
|
|
105
105
|
const match = ROUTE_SOURCE_RE.exec(key);
|
|
@@ -165,7 +165,7 @@ export function tryParseRouteModuleKey(key: string): ParsedRouteModuleKey | null
|
|
|
165
165
|
|
|
166
166
|
export function normalizeRoutePattern(segments: string[]): string {
|
|
167
167
|
const parts = segments.map(routeSegmentToPatternPart).filter((part) => part.length > 0);
|
|
168
|
-
return `/${parts.join("/")}
|
|
168
|
+
return trimTrailingSlashes(`/${parts.join("/")}`) || "/";
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
export function routeSegmentToPatternPart(segment: string): string {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const trimTrailingSlashes = (value: string) => {
|
|
2
|
+
let end = value.length;
|
|
3
|
+
while (end > 0 && value[end - 1] === "/") end -= 1;
|
|
4
|
+
return value.slice(0, end);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const trimSlashes = (value: string) => {
|
|
8
|
+
let start = 0;
|
|
9
|
+
while (start < value.length && value[start] === "/") start += 1;
|
|
10
|
+
return trimTrailingSlashes(value.slice(start));
|
|
11
|
+
};
|
package/common/types.ts
CHANGED
package/constant/fieldInfo.ts
CHANGED
|
@@ -276,7 +276,7 @@ export type FieldInfoObjectToFieldObject<Obj extends FieldInfoObject> = {
|
|
|
276
276
|
|
|
277
277
|
export class ConstantField<
|
|
278
278
|
FieldType extends ConstantFieldKind = ConstantFieldKind,
|
|
279
|
-
|
|
279
|
+
_Value extends ConstantFieldTypeInput | null = any,
|
|
280
280
|
FieldValue = any,
|
|
281
281
|
MapValue = any,
|
|
282
282
|
Nullable extends boolean = boolean,
|
|
@@ -472,7 +472,7 @@ type FieldOption<
|
|
|
472
472
|
>
|
|
473
473
|
>[];
|
|
474
474
|
|
|
475
|
-
export type PlainTypeToFieldType<PlainType> = PlainType extends [infer First, ...
|
|
475
|
+
export type PlainTypeToFieldType<PlainType> = PlainType extends [infer First, ...unknown[]]
|
|
476
476
|
? PlainTypeToFieldType<First>[]
|
|
477
477
|
: PlainType extends number
|
|
478
478
|
? typeof Int | typeof Float
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { FIELD_META, PrimitiveRegistry, type PrimitiveScalar } from "akanjs/base";
|
|
2
|
+
|
|
3
|
+
import type { ConstantField, FieldObject } from "./fieldInfo";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What a response does with a field: leaves it out (`hidden` / `secret`), computes it (`resolve`), copies it (a
|
|
7
|
+
* primitive or a Map), walks into it (an embedded scalar), or loads what it points at (a relation).
|
|
8
|
+
*/
|
|
9
|
+
export type FieldPlanKind = "masked" | "computed" | "value" | "scalar" | "relation";
|
|
10
|
+
|
|
11
|
+
export interface FieldPlanEntry {
|
|
12
|
+
key: string;
|
|
13
|
+
field: ConstantField;
|
|
14
|
+
kind: FieldPlanKind;
|
|
15
|
+
/** Points at another stored model, so an input carries its id; independent of `kind`, as `serialize` reads it. */
|
|
16
|
+
isRelationRef: boolean;
|
|
17
|
+
/** The scalar class of a primitive field, which `serialize` calls straight; `null` for everything else. */
|
|
18
|
+
primitive: typeof PrimitiveScalar | null;
|
|
19
|
+
serializeOption: { nullable: boolean; key: string };
|
|
20
|
+
}
|
|
21
|
+
interface PlanOwner {
|
|
22
|
+
[FIELD_META]: FieldObject;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class FieldPlan {
|
|
26
|
+
static #byClass = new WeakMap<object, FieldPlan>();
|
|
27
|
+
static #generation = 0;
|
|
28
|
+
static #computingAsync = new Set<FieldPlan>();
|
|
29
|
+
static of(cls: PlanOwner): FieldPlan {
|
|
30
|
+
const cached = FieldPlan.#byClass.get(cls);
|
|
31
|
+
if (cached) return cached;
|
|
32
|
+
const plan = new FieldPlan(cls[FIELD_META]);
|
|
33
|
+
FieldPlan.#byClass.set(cls, plan);
|
|
34
|
+
return plan;
|
|
35
|
+
}
|
|
36
|
+
static reset(cls: object) {
|
|
37
|
+
FieldPlan.#byClass.delete(cls);
|
|
38
|
+
FieldPlan.#generation += 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
readonly entries: readonly FieldPlanEntry[];
|
|
42
|
+
#resolvesAsync: { value: boolean; generation: number } | null = null;
|
|
43
|
+
constructor(fieldObj: FieldObject) {
|
|
44
|
+
this.entries = Object.entries(fieldObj).map(([key, field]) => ({
|
|
45
|
+
key,
|
|
46
|
+
field,
|
|
47
|
+
kind: FieldPlan.#kindOf(field),
|
|
48
|
+
isRelationRef: field.isClass && !field.isScalar,
|
|
49
|
+
primitive: PrimitiveRegistry.has(field.modelRef) ? (field.modelRef as unknown as typeof PrimitiveScalar) : null,
|
|
50
|
+
serializeOption: { nullable: field.nullable, key },
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static #kindOf(field: ConstantField): FieldPlanKind {
|
|
55
|
+
if (field.fieldType === "hidden" || field.fieldType === "secret") return "masked";
|
|
56
|
+
if (field.fieldType === "resolve") return "computed";
|
|
57
|
+
if (!field.isClass) return "value";
|
|
58
|
+
return field.isScalar ? "scalar" : "relation";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Whether resolving a value of this class awaits anything: a relation, a resolve field, or one in a scalar below. */
|
|
62
|
+
get resolvesAsync(): boolean {
|
|
63
|
+
if (this.#resolvesAsync?.generation === FieldPlan.#generation) return this.#resolvesAsync.value;
|
|
64
|
+
|
|
65
|
+
if (FieldPlan.#computingAsync.has(this)) return true;
|
|
66
|
+
FieldPlan.#computingAsync.add(this);
|
|
67
|
+
try {
|
|
68
|
+
const value = this.entries.some(
|
|
69
|
+
({ kind, field }) =>
|
|
70
|
+
kind === "computed" ||
|
|
71
|
+
kind === "relation" ||
|
|
72
|
+
(kind === "scalar" && FieldPlan.of(field.modelRef as unknown as PlanOwner).resolvesAsync),
|
|
73
|
+
);
|
|
74
|
+
this.#resolvesAsync = { value, generation: FieldPlan.#generation };
|
|
75
|
+
return value;
|
|
76
|
+
} finally {
|
|
77
|
+
FieldPlan.#computingAsync.delete(this);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
package/constant/index.ts
CHANGED
|
@@ -3,10 +3,13 @@ export * from "./constantRegistry";
|
|
|
3
3
|
export * from "./crystalize";
|
|
4
4
|
export * from "./dateSlot";
|
|
5
5
|
export * from "./fieldInfo";
|
|
6
|
+
export * from "./fieldPlan";
|
|
6
7
|
export * from "./fieldQueryMeta";
|
|
7
8
|
export * from "./getDefault";
|
|
8
9
|
export * from "./hydrationPlan";
|
|
9
10
|
export * from "./immerify";
|
|
11
|
+
export * from "./inputParser";
|
|
12
|
+
export * from "./invalidInputError";
|
|
10
13
|
export * from "./labelOf";
|
|
11
14
|
export * from "./mask";
|
|
12
15
|
export * from "./purify";
|