akanjs 3.1.1 → 3.1.2-rc.0
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/README.ko.md +22 -0
- package/README.md +22 -0
- package/client/cookie.ts +23 -7
- package/client/csrTypes.ts +1 -1
- package/client/makePageProto.tsx +10 -1
- package/client/router.ts +57 -16
- package/common/IpLiteral.ts +4 -2
- package/common/TrustedProxy.ts +45 -2
- package/common/akanNames.ts +169 -0
- package/common/codeAgentProfile.ts +51 -4
- package/common/index.ts +13 -0
- package/common/localeConfig.ts +30 -5
- package/common/localePath.ts +26 -0
- package/common/markdownSpans.ts +3 -1
- package/common/mcpExposure.ts +7 -7
- package/common/websocketContract.ts +50 -0
- package/constant/index.ts +1 -0
- package/constant/inputParser.ts +10 -2
- package/constant/requestValue.ts +47 -0
- package/dictionary/agent.dictionary.ts +2 -2
- package/dictionary/base.dictionary.ts +4 -0
- package/dictionary/dictInfo.ts +9 -9
- package/document/database.ts +1 -0
- package/document/documentQuery.ts +42 -6
- package/document/filterMeta.ts +56 -8
- package/document/queryEvaluator.ts +13 -8
- package/fetch/client/fetchClient.ts +28 -62
- package/fetch/client/httpClient.ts +1 -0
- package/fetch/client/sliceInitHandle.ts +18 -18
- package/fetch/index.ts +1 -0
- package/fetch/sessionCookie.ts +37 -0
- package/index.ts +8 -0
- package/package.json +13 -27
- package/server/akanApp.ts +108 -29
- package/server/akanServer.ts +18 -7
- package/server/assetHeaders.ts +20 -1
- package/server/cachePolicy.ts +18 -5
- package/server/devtools/depsSerializer.ts +2 -1
- package/server/devtools/devtoolsRouter.ts +3 -2
- package/server/devtools/signalSerializer.ts +5 -5
- package/server/hmr/clientReport.ts +10 -5
- package/server/imageOptimizer.ts +117 -22
- package/server/lifecycle/buildEnv.ts +11 -0
- package/server/lifecycle/loadBuildEnv.ts +3 -0
- package/server/logging/logControlSocket.ts +20 -14
- package/server/logging/logStreamRoute.ts +12 -8
- package/server/logging/rotatingLogWriter.ts +15 -1
- package/server/machine/machineToken.ts +4 -0
- package/server/mcp/McpAuth.ts +12 -4
- package/server/mcp/McpExecutionContext.ts +4 -0
- package/server/mcp/McpRouter.ts +25 -13
- package/server/mcp/PagePromptComposer.ts +4 -7
- package/server/oauth/OAuthClientIdMetadata.ts +29 -6
- package/server/oauth/OAuthRegistration.ts +9 -0
- package/server/ops/failureLimiter.ts +42 -0
- package/server/ops/opsCommand.ts +19 -7
- package/server/ops/opsRoute.ts +60 -9
- package/server/ops/opsToken.ts +5 -0
- package/server/ops/snapshotJobs.ts +3 -1
- package/server/ops/snapshotRestore.ts +23 -1
- package/server/ops/sqliteSnapshot.ts +98 -18
- package/server/ops/statusAccess.ts +37 -19
- package/server/proxy/hostBasePathWebProxy.ts +4 -7
- package/server/proxy/localeWebProxy.ts +36 -16
- package/server/resolver/database.resolver.ts +15 -11
- package/server/resolver/service.resolver.ts +17 -2
- package/server/resolver/signal.resolver.ts +50 -40
- package/server/routeElementComposer.tsx +42 -9
- package/server/routeShellGate.ts +42 -0
- package/server/routing/apiRouter.ts +64 -1
- package/server/routing/serverLimits.ts +151 -0
- package/server/routing/soloAppRoutes.ts +23 -12
- package/server/rscClient.tsx +5 -3
- package/server/rscScrollRestoration.ts +60 -9
- package/server/rscWorker.tsx +76 -24
- package/server/sitemap.ts +5 -7
- package/server/ssrFromRscRenderer.tsx +36 -4
- package/server/ssrTypes.ts +5 -2
- package/server/staticPath.ts +36 -1
- package/server/systemPageDocument.tsx +5 -3
- package/server/systemPages.tsx +14 -8
- package/server/tunnel/TunnelAgent.ts +19 -2
- package/server/tunnel/TunnelDataSocket.ts +4 -2
- package/server/tunnel/TunnelHttpStream.ts +7 -2
- package/server/tunnel/TunnelTcpStream.ts +6 -3
- package/server/tunnel/TunnelWebsocketStream.ts +9 -2
- package/server/tunnel/tunnelStream.ts +6 -0
- package/server/types.tsx +20 -2
- package/server/webRouter.ts +38 -18
- package/service/agent.service.ts +9 -7
- package/service/injectInfo.ts +2 -1
- package/service/liveSyncHub.ts +6 -0
- package/service/predefinedAdaptor/anthropicLlm.ts +13 -12
- package/service/predefinedAdaptor/database.adaptor.ts +4 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +3 -0
- package/service/predefinedAdaptor/llmOverflow.ts +5 -1
- package/service/predefinedAdaptor/openaiDialect.ts +9 -8
- package/service/predefinedAdaptor/searchIndex.ts +21 -1
- package/service/predefinedAdaptor/sql/QueryCompiler.ts +28 -4
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +45 -11
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +6 -1
- package/service/predefinedAdaptor/sql/types.ts +3 -0
- package/service/predefinedAdaptor/storage.adaptor.ts +11 -10
- package/service/serve.ts +3 -3
- package/service/serviceModule.ts +28 -24
- package/service/types.ts +3 -0
- package/signal/SignalFailure.ts +5 -2
- package/signal/agent/AgentCatalogue.ts +3 -2
- package/signal/agentMeter.ts +81 -9
- package/signal/agentRelayPolicy.ts +53 -2
- package/signal/agentTurnStream.ts +12 -1
- package/signal/guard.ts +2 -2
- package/signal/mcp/McpDocument.ts +3 -3
- package/signal/mcp/McpUriTemplate.ts +5 -5
- package/signal/middleware.ts +15 -1
- package/signal/roomKey.ts +53 -0
- package/signal/signalContext.ts +100 -40
- package/signal/slice.ts +27 -22
- package/signal/trace.ts +17 -6
- package/store/action.ts +20 -49
- package/store/actionTag.ts +2 -0
- package/store/agent/ScreenReader.ts +36 -4
- package/store/agent/ScreenTarget.ts +1 -1
- package/store/agent/StoreCatalogue.ts +7 -1
- package/store/agent/StoreSurfaceSource.ts +5 -0
- package/store/agentic/AgentConfirm.ts +30 -0
- package/store/agentic/StStateBuilder.ts +2 -2
- package/store/agentic/StToolBuilder.ts +7 -2
- package/store/agentic/formFields.ts +1 -2
- package/store/agentic/index.ts +1 -0
- package/store/agentic/useFieldTool.ts +4 -3
- package/store/deprecatedNames.ts +6 -0
- package/store/draftStore.ts +2 -2
- package/store/formUploadOf.ts +6 -5
- package/store/index.ts +1 -2
- package/store/sliceKeys.ts +9 -32
- package/store/state.ts +1 -1
- package/store/stateBuilder.ts +1 -1
- package/store/store.ts +1 -1
- package/store/storeInstance.ts +12 -8
- package/store/storeRegistry.ts +14 -2
- package/types/client/cookie.d.ts +7 -2
- package/types/client/csrTypes.d.ts +1 -1
- package/types/client/router.d.ts +4 -1
- package/types/common/akanNames.d.ts +149 -0
- package/types/common/codeAgentProfile.d.ts +4 -1
- package/types/common/index.d.ts +3 -1
- package/types/common/localeConfig.d.ts +8 -0
- package/types/common/localePath.d.ts +13 -0
- package/types/common/websocketContract.d.ts +7 -0
- package/types/constant/index.d.ts +1 -0
- package/types/constant/requestValue.d.ts +5 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +3 -3
- package/types/document/database.d.ts +1 -0
- package/types/document/documentQuery.d.ts +3 -0
- package/types/document/filterMeta.d.ts +4 -0
- package/types/fetch/client/sliceInitHandle.d.ts +1 -2
- package/types/fetch/index.d.ts +1 -0
- package/types/fetch/sessionCookie.d.ts +12 -0
- package/types/index.d.ts +8 -0
- package/types/server/akanApp.d.ts +1 -0
- package/types/server/assetHeaders.d.ts +5 -0
- package/types/server/cachePolicy.d.ts +2 -0
- package/types/server/devtools/devtoolsRouter.d.ts +1 -1
- package/types/server/imageOptimizer.d.ts +4 -1
- package/types/server/lifecycle/buildEnv.d.ts +2 -0
- package/types/server/lifecycle/loadBuildEnv.d.ts +1 -0
- package/types/server/logging/logStreamRoute.d.ts +1 -1
- package/types/server/logging/rotatingLogWriter.d.ts +3 -0
- package/types/server/machine/machineToken.d.ts +2 -0
- package/types/server/mcp/McpAuth.d.ts +2 -1
- package/types/server/mcp/McpExecutionContext.d.ts +1 -0
- package/types/server/oauth/OAuthRegistration.d.ts +2 -0
- package/types/server/ops/failureLimiter.d.ts +11 -0
- package/types/server/ops/opsRoute.d.ts +4 -2
- package/types/server/ops/opsToken.d.ts +2 -0
- package/types/server/ops/snapshotRestore.d.ts +3 -1
- package/types/server/ops/sqliteSnapshot.d.ts +7 -1
- package/types/server/ops/statusAccess.d.ts +8 -4
- package/types/server/proxy/hostBasePathWebProxy.d.ts +1 -0
- package/types/server/resolver/signal.resolver.d.ts +0 -1
- package/types/server/routeElementComposer.d.ts +10 -4
- package/types/server/routeShellGate.d.ts +12 -0
- package/types/server/routing/apiRouter.d.ts +6 -1
- package/types/server/routing/serverLimits.d.ts +23 -0
- package/types/server/routing/soloAppRoutes.d.ts +1 -1
- package/types/server/rscScrollRestoration.d.ts +1 -0
- package/types/server/ssrTypes.d.ts +5 -2
- package/types/server/staticPath.d.ts +1 -0
- package/types/server/systemPageDocument.d.ts +1 -0
- package/types/server/tunnel/TunnelAgent.d.ts +6 -0
- package/types/server/tunnel/TunnelDataSocket.d.ts +2 -0
- package/types/server/tunnel/TunnelTcpStream.d.ts +1 -1
- package/types/server/tunnel/tunnelStream.d.ts +1 -0
- package/types/server/types.d.ts +11 -2
- package/types/service/agent.service.d.ts +1 -1
- package/types/service/liveSyncHub.d.ts +1 -0
- package/types/service/predefinedAdaptor/anthropicLlm.d.ts +1 -1
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +1 -0
- package/types/service/predefinedAdaptor/openaiDialect.d.ts +1 -1
- package/types/service/predefinedAdaptor/searchIndex.d.ts +5 -1
- package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/types.d.ts +3 -0
- package/types/service/types.d.ts +3 -0
- package/types/signal/SignalFailure.d.ts +1 -1
- package/types/signal/agentMeter.d.ts +4 -1
- package/types/signal/agentRelayPolicy.d.ts +12 -0
- package/types/signal/guard.d.ts +1 -1
- package/types/signal/roomKey.d.ts +8 -0
- package/types/signal/signalContext.d.ts +20 -6
- package/types/signal/slice.d.ts +1 -0
- package/types/signal/trace.d.ts +4 -0
- package/types/store/actionTag.d.ts +2 -0
- package/types/store/agent/ScreenReader.d.ts +2 -1
- package/types/store/agent/StoreCatalogue.d.ts +5 -0
- package/types/store/agentic/AgentConfirm.d.ts +15 -0
- package/types/store/agentic/StToolBuilder.d.ts +3 -1
- package/types/store/agentic/index.d.ts +1 -0
- package/types/store/deprecatedNames.d.ts +5 -0
- package/types/store/index.d.ts +1 -2
- package/types/store/sliceKeys.d.ts +12 -2
- package/types/ui/Agent/agentSessionOf.d.ts +2 -0
- package/types/ui/Agent/useAccountBoundSession.d.ts +2 -0
- package/types/ui/Agent/useScreenActions.d.ts +1 -1
- package/types/ui/Icon.d.ts +15 -0
- package/types/ui/animated.d.ts +3 -3
- package/types/ui/sliceNamesOf.d.ts +4 -4
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +10 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +6 -0
- package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +6 -1
- package/types/vendor/@akanjs/native/packages/desktop/src/paths.d.ts +7 -0
- package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +2 -0
- package/types/vendor/@akanjs/native/plugins/updates/src/desktop.d.ts +2 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +6 -1
- package/types/vendor/use-agentic/ToolRunner.d.ts +7 -2
- package/types/vendor/use-agentic/types.d.ts +2 -0
- package/types/vendor/use-agentic/useAgentTool.d.ts +2 -1
- package/types/webkit/useCsrValues.d.ts +3 -3
- package/ui/Agent/Chat.tsx +2 -0
- package/ui/Agent/Zone.tsx +2 -0
- package/ui/Agent/agentSessionOf.ts +54 -5
- package/ui/Agent/useAccountBoundSession.ts +26 -0
- package/ui/Agent/useScreenActions.ts +1 -1
- package/ui/BottomSheet.tsx +1 -1
- package/ui/Button.tsx +5 -5
- package/ui/Data/Item.tsx +2 -8
- package/ui/Data/ListContainer.tsx +4 -4
- package/ui/Data/QueryMaker.tsx +3 -2
- package/ui/Data/RefPicker.tsx +3 -2
- package/ui/Data/UnlistedEditModal.tsx +2 -1
- package/ui/Data/ViewActions.tsx +4 -4
- package/ui/Data/dataExport.ts +3 -0
- package/ui/Data/useViewModal.ts +10 -15
- package/ui/Dialog/LegacyModal.tsx +1 -1
- package/ui/Dialog/Provider.tsx +2 -2
- package/ui/DragAction.tsx +1 -1
- package/ui/Dropdown.tsx +2 -2
- package/ui/Field/lightRefCache.ts +2 -2
- package/ui/Field/useRelationOptions.ts +3 -10
- package/ui/Icon.tsx +87 -0
- package/ui/KeyboardAvoiding.tsx +1 -1
- package/ui/Layout/Sider.tsx +3 -3
- package/ui/Link/Anchor.tsx +4 -1
- package/ui/Load/Edit.tsx +2 -2
- package/ui/Load/Edit_Client.tsx +3 -3
- package/ui/Load/Pagination.tsx +3 -3
- package/ui/Load/View.tsx +15 -14
- package/ui/Loading/ProgressBar.tsx +1 -1
- package/ui/Model/DraftBar.tsx +7 -14
- package/ui/Model/EditModal.tsx +9 -40
- package/ui/Model/EditWrapper.tsx +3 -5
- package/ui/Model/NewWrapper_Client.tsx +3 -7
- package/ui/Model/Remove.tsx +2 -2
- package/ui/Model/RemoveWrapper.tsx +2 -4
- package/ui/Model/SureToRemove.tsx +2 -2
- package/ui/Model/ViewEditModal.tsx +9 -20
- package/ui/Model/ViewModal.tsx +4 -11
- package/ui/Model/ViewWrapper.tsx +4 -6
- package/ui/ScreenNavigator.tsx +2 -2
- package/ui/Signal/WebSocket.tsx +2 -2
- package/ui/System/CSR.tsx +3 -3
- package/ui/System/Reconnect.tsx +2 -2
- package/ui/System/SSR.tsx +2 -2
- package/ui/System/SelectLanguage.tsx +1 -1
- package/ui/System/ThemeToggle.tsx +1 -1
- package/ui/Tab/Provider.tsx +1 -0
- package/ui/Toast.tsx +7 -15
- package/ui/index.ts +0 -1
- package/ui/sliceNamesOf.ts +25 -15
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +2 -1
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +10 -2
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +4 -0
- package/vendor/@akanjs/native/native/desktop/src/acl.rs +166 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +25 -6
- package/vendor/@akanjs/native/native/desktop/src/navigation.rs +22 -10
- package/vendor/@akanjs/native/native/desktop/src/routes.rs +2 -2
- package/vendor/@akanjs/native/native/desktop/src/vectors.rs +28 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeBridge.swift +1 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +10 -2
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +4 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +1 -0
- package/vendor/@akanjs/native/packages/cli/src/config.ts +10 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/csp.ts +5 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +4 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +30 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +1 -1
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +18 -2
- package/vendor/@akanjs/native/packages/core/vectors/navigation.json +22 -1
- package/vendor/@akanjs/native/packages/core/vectors/routes.json +4 -0
- package/vendor/@akanjs/native/packages/desktop/src/main.ts +5 -1
- package/vendor/@akanjs/native/packages/desktop/src/paths.ts +24 -1
- package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +4 -5
- package/vendor/@akanjs/native/plugins/file-picker/src/desktop.ts +6 -8
- package/vendor/@akanjs/native/plugins/filesystem/src/desktop.ts +4 -2
- package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +22 -3
- package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +6 -0
- package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +6 -0
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +106 -18
- package/vendor/use-agentic/AgentSession.ts +7 -2
- package/vendor/use-agentic/ToolRunner.ts +44 -5
- package/vendor/use-agentic/types.ts +2 -0
- package/vendor/use-agentic/useAgentTool.ts +4 -2
- package/webkit/McpAppAgent.ts +2 -1
- package/webkit/bootCsr.tsx +1 -1
- package/webkit/useCsrValues.ts +5 -1
- package/webkit/usePageTool.tsx +1 -0
- package/store/databaseStateNames.ts +0 -24
- package/store/formSetterNames.ts +0 -18
- package/types/store/databaseStateNames.d.ts +0 -18
- package/types/store/formSetterNames.d.ts +0 -11
package/document/filterMeta.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Cls, EnumInstance, MergeAllDoubleKeyOfObjects } from "akanjs/base";
|
|
2
|
-
import { FILTER_DICT_SHAPE, FILTER_META, getNonArrayModel, isEnum } from "akanjs/base";
|
|
2
|
+
import { Any, FILTER_DICT_SHAPE, FILTER_META, getNonArrayModel, isEnum, PrimitiveRegistry } from "akanjs/base";
|
|
3
3
|
import {
|
|
4
4
|
type BaseObject,
|
|
5
5
|
type ConstantFieldType,
|
|
6
6
|
type ConstantFieldTypeInput,
|
|
7
7
|
type DocumentModel,
|
|
8
|
-
deserialize,
|
|
9
8
|
type FieldToValue,
|
|
9
|
+
InputParser,
|
|
10
10
|
type PlainTypeToFieldType,
|
|
11
11
|
type QueryOf,
|
|
12
12
|
type Serialized,
|
|
@@ -66,7 +66,8 @@ export const getFilterInfoByKey = <ArgNames extends string[] = [], Args extends
|
|
|
66
66
|
modelRef: FilterCls,
|
|
67
67
|
key: string,
|
|
68
68
|
): FilterInfo<ArgNames, Args, Model> => {
|
|
69
|
-
const
|
|
69
|
+
const { query } = getFilterMeta(modelRef);
|
|
70
|
+
const queryMeta = Object.hasOwn(query, key) ? query[key] : undefined;
|
|
70
71
|
if (!queryMeta) throw new Error(`queryMeta is not defined for key: ${key}`);
|
|
71
72
|
return queryMeta;
|
|
72
73
|
};
|
|
@@ -78,7 +79,10 @@ export const setFilterInfoByKey = <ArgNames extends string[] = [], Args extends
|
|
|
78
79
|
const filterMeta = getFilterMeta(modelRef);
|
|
79
80
|
Object.assign(filterMeta.query, { [key]: filterInfo });
|
|
80
81
|
};
|
|
81
|
-
export const getFilterSortByKey = (modelRef: FilterCls, key: string) =>
|
|
82
|
+
export const getFilterSortByKey = (modelRef: FilterCls, key: string) => {
|
|
83
|
+
const { sort } = getFilterMeta(modelRef);
|
|
84
|
+
return Object.hasOwn(sort, key) ? sort[key] : undefined;
|
|
85
|
+
};
|
|
82
86
|
|
|
83
87
|
export const fillMissingFilterArgs = (filterInfo: FilterInfo, args: unknown[]) => {
|
|
84
88
|
if (args.length >= filterInfo.args.length) return args;
|
|
@@ -110,7 +114,10 @@ export interface FilterArgInfo {
|
|
|
110
114
|
enum?: EnumInstance;
|
|
111
115
|
nullable: boolean;
|
|
112
116
|
ref?: string;
|
|
117
|
+
maxItems: number;
|
|
113
118
|
}
|
|
119
|
+
|
|
120
|
+
export const DEFAULT_FILTER_ARG_MAX_ITEMS = 1000;
|
|
114
121
|
/** Unwraps a filter arg's declaration the way `EndpointInfo.getArgInfo` unwraps an endpoint's. */
|
|
115
122
|
export const getFilterArgInfo = (arg: FilterInfo["args"][number]): FilterArgInfo => {
|
|
116
123
|
const [singleArg, arrDepth] = getNonArrayModel(arg.argRef as Cls);
|
|
@@ -122,6 +129,7 @@ export const getFilterArgInfo = (arg: FilterInfo["args"][number]): FilterArgInfo
|
|
|
122
129
|
enum: argIsEnum ? (singleArg as EnumInstance) : undefined,
|
|
123
130
|
nullable: !!arg.option?.nullable,
|
|
124
131
|
ref: arg.option?.ref,
|
|
132
|
+
maxItems: arg.option?.maxItems ?? DEFAULT_FILTER_ARG_MAX_ITEMS,
|
|
125
133
|
};
|
|
126
134
|
};
|
|
127
135
|
export const getFilterArgInfos = (filterInfo: FilterInfo): FilterArgInfo[] => filterInfo.args.map(getFilterArgInfo);
|
|
@@ -129,9 +137,37 @@ export const getFilterArgInfos = (filterInfo: FilterInfo): FilterArgInfo[] => fi
|
|
|
129
137
|
/** A caller named a filter that does not exist, or gave it arguments it cannot take. Its callers answer 400. */
|
|
130
138
|
export class FilterQueryError extends Error {}
|
|
131
139
|
|
|
132
|
-
const
|
|
140
|
+
const countItems = (value: unknown, arrDepth: number): number =>
|
|
141
|
+
arrDepth && Array.isArray(value) ? value.reduce((sum: number, item) => sum + countItems(item, arrDepth - 1), 0) : 1;
|
|
142
|
+
|
|
143
|
+
const isPlainContainer = (value: unknown) => {
|
|
144
|
+
if (!value || typeof value !== "object") return false;
|
|
145
|
+
const proto = Object.getPrototypeOf(value);
|
|
146
|
+
return Array.isArray(value) || proto === Object.prototype || proto === null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const primitiveShapeProblem = (value: unknown, arrDepth: number): string | null => {
|
|
150
|
+
if (!arrDepth) return isPlainContainer(value) ? "a single value" : null;
|
|
151
|
+
if (!Array.isArray(value)) return "a list";
|
|
152
|
+
for (const item of value) {
|
|
153
|
+
const problem = primitiveShapeProblem(item, arrDepth - 1);
|
|
154
|
+
if (problem) return problem;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const parseFilterArg = (arg: FilterArgInfo, value: unknown, key: string) => {
|
|
160
|
+
const shapeProblem = PrimitiveRegistry.has(arg.argRef as Cls) ? primitiveShapeProblem(value, arg.arrDepth) : null;
|
|
161
|
+
if (shapeProblem && value !== null && value !== undefined)
|
|
162
|
+
throw new FilterQueryError(`Invalid filter argument "${arg.name}" for key: ${key}: expected ${shapeProblem}`);
|
|
163
|
+
if (arg.arrDepth && countItems(value, arg.arrDepth) > arg.maxItems)
|
|
164
|
+
throw new FilterQueryError(`Filter argument "${arg.name}" for key: ${key} takes at most ${arg.maxItems} items`);
|
|
133
165
|
try {
|
|
134
|
-
return
|
|
166
|
+
return InputParser.parse(arg.argRef as Cls, arg.arrDepth, value, {
|
|
167
|
+
key: arg.name,
|
|
168
|
+
nullable: arg.nullable,
|
|
169
|
+
enum: arg.enum,
|
|
170
|
+
});
|
|
135
171
|
} catch (error) {
|
|
136
172
|
throw new FilterQueryError(
|
|
137
173
|
`Invalid filter argument "${arg.name}" for key: ${key}: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -149,7 +185,8 @@ export const resolveFilterQuery = (
|
|
|
149
185
|
args?: unknown[] | null,
|
|
150
186
|
): QueryOf<any> => {
|
|
151
187
|
const key = queryKey || "any";
|
|
152
|
-
const
|
|
188
|
+
const { query } = getFilterMeta(filterRef);
|
|
189
|
+
const filterInfo = Object.hasOwn(query, key) ? query[key] : undefined;
|
|
153
190
|
const queryFn = filterInfo?.queryFn;
|
|
154
191
|
if (!queryFn) throw new FilterQueryError(`No filter query for key: ${key}`);
|
|
155
192
|
const given = Array.isArray(args) ? args : [];
|
|
@@ -157,7 +194,7 @@ export const resolveFilterQuery = (
|
|
|
157
194
|
const value = given[idx];
|
|
158
195
|
if (!arg.nullable && (value === null || value === undefined))
|
|
159
196
|
throw new FilterQueryError(`Missing filter argument "${arg.name}" for key: ${key}`);
|
|
160
|
-
const parsed =
|
|
197
|
+
const parsed = parseFilterArg(arg, value, key);
|
|
161
198
|
|
|
162
199
|
return parsed === null ? undefined : parsed;
|
|
163
200
|
});
|
|
@@ -276,6 +313,7 @@ interface ArgProps<Value = unknown> {
|
|
|
276
313
|
nullable?: boolean;
|
|
277
314
|
ref?: string;
|
|
278
315
|
default?: Value;
|
|
316
|
+
maxItems?: number;
|
|
279
317
|
}
|
|
280
318
|
export class FilterInfo<ArgNames extends string[] = any, Args extends any[] = any, Model = any> {
|
|
281
319
|
readonly argNames: ArgNames = [] as unknown as ArgNames;
|
|
@@ -293,6 +331,7 @@ export class FilterInfo<ArgNames extends string[] = any, Args extends any[] = an
|
|
|
293
331
|
>(name: ArgName, argRef: Arg, option?: Omit<ArgProps<_FieldToValue>, "nullable">) {
|
|
294
332
|
if (this.queryFn) throw new Error("Query function is already set");
|
|
295
333
|
else if (this.args.at(-1)?.option?.nullable) throw new Error("Last argument is nullable");
|
|
334
|
+
FilterInfo.#assertTyped(name, argRef);
|
|
296
335
|
this.argNames.push(name);
|
|
297
336
|
this.args.push({ name, argRef, option });
|
|
298
337
|
return this as unknown as FilterInfo<[...ArgNames, ArgName], [...Args, arg: _FieldToValue], Model>;
|
|
@@ -304,6 +343,7 @@ export class FilterInfo<ArgNames extends string[] = any, Args extends any[] = an
|
|
|
304
343
|
_FieldToValue = DocumentModel<FieldToValue<Arg>>,
|
|
305
344
|
>(name: ArgName, argRef: Arg, option?: Omit<ArgProps<_FieldToValue>, "nullable">) {
|
|
306
345
|
if (this.queryFn) throw new Error("Query function is already set");
|
|
346
|
+
FilterInfo.#assertTyped(name, argRef);
|
|
307
347
|
this.argNames.push(name);
|
|
308
348
|
this.args.push({ name, argRef, option: { ...option, nullable: true } });
|
|
309
349
|
return this as unknown as FilterInfo<[...ArgNames, ArgName], [...Args, opt?: _FieldToValue | null], Model>;
|
|
@@ -313,6 +353,14 @@ export class FilterInfo<ArgNames extends string[] = any, Args extends any[] = an
|
|
|
313
353
|
this.queryFn = query;
|
|
314
354
|
return this;
|
|
315
355
|
}
|
|
356
|
+
|
|
357
|
+
static #assertTyped(name: string, argRef: ConstantFieldTypeInput) {
|
|
358
|
+
const [single] = getNonArrayModel(argRef as Cls);
|
|
359
|
+
if (single === Any)
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Filter argument "${name}" is declared as Any; declare its type (a primitive, an enum or a scalar) so a caller's value is checked against it`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
316
364
|
}
|
|
317
365
|
|
|
318
366
|
export const filter = () => new FilterInfo<[], [], any>();
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
type DocumentQueryNode,
|
|
5
5
|
type DocumentQueryOperator,
|
|
6
6
|
encodeDocumentValue,
|
|
7
|
+
fieldValueProblem,
|
|
7
8
|
isDocumentQueryNode,
|
|
9
|
+
queryNodeProblem,
|
|
8
10
|
queryOperatorKeys,
|
|
9
11
|
sanitizeJson,
|
|
10
12
|
} from "./documentQuery";
|
|
@@ -51,6 +53,8 @@ export class DocumentQueryEvaluator {
|
|
|
51
53
|
static unevaluableReason(query: DocumentQuery | undefined): string | null {
|
|
52
54
|
if (!query || typeof query !== "object") return null;
|
|
53
55
|
if (isDocumentQueryNode(query)) {
|
|
56
|
+
const problem = queryNodeProblem(query);
|
|
57
|
+
if (problem) return problem;
|
|
54
58
|
if (query.kind === "raw") return "q.raw() carries SQL that only the database can run";
|
|
55
59
|
if (query.kind === "search") return "q.search() compiles to a full-text JOIN with no in-memory equivalent";
|
|
56
60
|
if (query.kind === "all" || query.kind === "any")
|
|
@@ -69,12 +73,12 @@ export class DocumentQueryEvaluator {
|
|
|
69
73
|
|
|
70
74
|
static #unevaluableField(path: string, value: unknown): string | null {
|
|
71
75
|
if (!value || typeof value !== "object") return null;
|
|
76
|
+
const problem = fieldValueProblem(path, value);
|
|
77
|
+
if (problem) return problem;
|
|
72
78
|
if (baseDocumentColumns.has(path)) return null;
|
|
73
79
|
const keyAbsence = `${path}: exists/missing asks whether the key is in the stored JSON, which a document in hand cannot answer — use q.empty()`;
|
|
74
|
-
if (isDocumentQueryNode(value))
|
|
75
|
-
|
|
76
|
-
return DocumentQueryEvaluator.unevaluableReason(value as DocumentQuery);
|
|
77
|
-
}
|
|
80
|
+
if (isDocumentQueryNode(value))
|
|
81
|
+
return value.kind === "op" && (value.op === "exists" || value.op === "missing") ? keyAbsence : null;
|
|
78
82
|
const operators = value as Record<string, unknown>;
|
|
79
83
|
|
|
80
84
|
if ("exists" in operators || "missing" in operators || ("empty" in operators && !operators.empty))
|
|
@@ -102,6 +106,8 @@ export class DocumentQueryEvaluator {
|
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
#node(node: DocumentQueryNode, row: DocumentRowView): boolean {
|
|
109
|
+
const problem = queryNodeProblem(node);
|
|
110
|
+
if (problem) throw new Error(problem);
|
|
105
111
|
switch (node.kind) {
|
|
106
112
|
|
|
107
113
|
case "all":
|
|
@@ -119,10 +125,9 @@ export class DocumentQueryEvaluator {
|
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
#field(path: string, value: unknown, row: DocumentRowView): boolean {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
128
|
+
const problem = fieldValueProblem(path, value);
|
|
129
|
+
if (problem) throw new Error(problem);
|
|
130
|
+
if (isDocumentQueryNode(value) && value.kind === "op") return this.#operator(path, value.op, value.value, row);
|
|
126
131
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
127
132
|
const operators = value as Record<string, unknown>;
|
|
128
133
|
const keys = Object.keys(operators);
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { getApiPrefix, getEnv, getWsPrefix, PrimitiveRegistry, type PromiseOrObject } from "akanjs/base";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
endpointNames,
|
|
4
4
|
type FetchPolicy,
|
|
5
5
|
fileUploadContract,
|
|
6
6
|
Logger,
|
|
7
|
+
modelNames,
|
|
7
8
|
type RetryPolicy,
|
|
8
9
|
readSessionToken,
|
|
9
10
|
resolveFileUploadCapability,
|
|
11
|
+
sliceNames,
|
|
10
12
|
trimTrailingSlashes,
|
|
11
13
|
} from "akanjs/common";
|
|
12
14
|
import { type BaseInsight, type BaseObject, ConstantRegistry, deserialize, serialize } from "akanjs/constant";
|
|
@@ -377,7 +379,7 @@ export class FetchClient {
|
|
|
377
379
|
Object.entries(this.handler).forEach(([key, handler]) => {
|
|
378
380
|
if (!(key in instance.handler)) instance.handler[key] = handler;
|
|
379
381
|
});
|
|
380
|
-
instance.setJwt(jwt ?? this.jwt);
|
|
382
|
+
instance.setJwt(jwt ?? (FetchClient.#sameOrigin(instance.origin, this.origin) ? this.jwt : null));
|
|
381
383
|
if (connect) instance.connect();
|
|
382
384
|
return FetchClient.#makeProxy(instance);
|
|
383
385
|
}
|
|
@@ -390,7 +392,6 @@ export class FetchClient {
|
|
|
390
392
|
setJwt(jwt: string | null) {
|
|
391
393
|
this.jwt = jwt;
|
|
392
394
|
this.ws.setJwt(jwt);
|
|
393
|
-
for (const ws of this.#originWs.values()) ws.setJwt(jwt);
|
|
394
395
|
}
|
|
395
396
|
|
|
396
397
|
#resolveWs(origin?: string) {
|
|
@@ -400,7 +401,6 @@ export class FetchClient {
|
|
|
400
401
|
if (cached) return cached;
|
|
401
402
|
const ws = new WsClient(FetchClient.#makeWsUri(target), this.ErrorCls).configure(this.#wsOptions);
|
|
402
403
|
this.#originWs.set(target, ws);
|
|
403
|
-
ws.setJwt(this.jwt);
|
|
404
404
|
ws.connect();
|
|
405
405
|
return ws;
|
|
406
406
|
}
|
|
@@ -425,9 +425,12 @@ export class FetchClient {
|
|
|
425
425
|
static #sameOrigin(a: string, b: string) {
|
|
426
426
|
return FetchClient.#normalizeOrigin(a) === FetchClient.#normalizeOrigin(b);
|
|
427
427
|
}
|
|
428
|
+
#ownOrigin(option?: FetchPolicy) {
|
|
429
|
+
return !option?.origin || FetchClient.#sameOrigin(option.origin, this.origin);
|
|
430
|
+
}
|
|
428
431
|
|
|
429
432
|
#forwardsVisitor(option?: FetchPolicy) {
|
|
430
|
-
return !this.#foreign &&
|
|
433
|
+
return !this.#foreign && this.#ownOrigin(option);
|
|
431
434
|
}
|
|
432
435
|
#makeAuthHeaders(option?: FetchPolicy): Record<string, string> {
|
|
433
436
|
if (option?.token) return { Authorization: `Bearer ${option.token}` };
|
|
@@ -441,7 +444,7 @@ export class FetchClient {
|
|
|
441
444
|
}
|
|
442
445
|
} catch {
|
|
443
446
|
}
|
|
444
|
-
return this.jwt ? { Authorization: `Bearer ${this.jwt}` } : {};
|
|
447
|
+
return this.jwt && this.#ownOrigin(option) ? { Authorization: `Bearer ${this.jwt}` } : {};
|
|
445
448
|
}
|
|
446
449
|
#baseUrlOf(endpoint: SerializedEndpoint, origin?: string) {
|
|
447
450
|
if (endpoint.globalPrefix !== false) return origin;
|
|
@@ -519,7 +522,7 @@ export class FetchClient {
|
|
|
519
522
|
this.#setHandlerFactory(key, () => this.#makeHttpFn(key, endpoint, prefix));
|
|
520
523
|
return;
|
|
521
524
|
case "pubsub": {
|
|
522
|
-
this.#setHandlerFactory(
|
|
525
|
+
this.#setHandlerFactory(endpointNames(key).subscribe, () => {
|
|
523
526
|
const roomArgs = endpoint.args.filter((arg) => arg.type === "room");
|
|
524
527
|
const roomArgLength = roomArgs.length;
|
|
525
528
|
const serializerMap = this.#makeArgSerializer(endpoint.args);
|
|
@@ -567,7 +570,7 @@ export class FetchClient {
|
|
|
567
570
|
this.#resolveWs(fetchPolicy?.origin).emit(key, data);
|
|
568
571
|
};
|
|
569
572
|
});
|
|
570
|
-
this.#setHandlerFactory(
|
|
573
|
+
this.#setHandlerFactory(endpointNames(key).listen, () => {
|
|
571
574
|
const parseReturn = this.#makeReturnParser(endpoint.returns);
|
|
572
575
|
const wrappedListeners = new WeakMap<(data: unknown) => void, (data: unknown) => void>();
|
|
573
576
|
return ((handleEvent: (data: unknown) => void, fetchPolicy: FetchPolicy = {}) => {
|
|
@@ -654,15 +657,7 @@ export class FetchClient {
|
|
|
654
657
|
}
|
|
655
658
|
|
|
656
659
|
static getBaseEndpoint(refName: string, signal: SerializedSignal) {
|
|
657
|
-
const
|
|
658
|
-
const names = {
|
|
659
|
-
createModel: `create${capRefName}`,
|
|
660
|
-
updateModel: `update${capRefName}`,
|
|
661
|
-
removeModel: `remove${capRefName}`,
|
|
662
|
-
model: refName,
|
|
663
|
-
modelId: `${refName}Id`,
|
|
664
|
-
lightModel: `light${capRefName}`,
|
|
665
|
-
};
|
|
660
|
+
const names = modelNames(refName);
|
|
666
661
|
const createGuards = FetchClient.#mountedGuards(signal, "create");
|
|
667
662
|
const updateGuards = FetchClient.#mountedGuards(signal, "update");
|
|
668
663
|
const removeGuards = FetchClient.#mountedGuards(signal, "remove");
|
|
@@ -725,22 +720,6 @@ export class FetchClient {
|
|
|
725
720
|
const guards = verb === "get" ? signal.getGuards : (signal[`${verb}Guards`] ?? signal.cruGuards);
|
|
726
721
|
return guards?.some((guard) => guard !== "None") ? guards : undefined;
|
|
727
722
|
}
|
|
728
|
-
static #modelNames(refName: string) {
|
|
729
|
-
const capRefName = capitalize(refName);
|
|
730
|
-
return {
|
|
731
|
-
createModel: `create${capRefName}`,
|
|
732
|
-
updateModel: `update${capRefName}`,
|
|
733
|
-
removeModel: `remove${capRefName}`,
|
|
734
|
-
model: refName,
|
|
735
|
-
lightModel: `light${capRefName}`,
|
|
736
|
-
viewModel: `view${capRefName}`,
|
|
737
|
-
getModelView: `get${capRefName}View`,
|
|
738
|
-
editModel: `edit${capRefName}`,
|
|
739
|
-
getModelEdit: `get${capRefName}Edit`,
|
|
740
|
-
addModelFiles: `add${capRefName}Files`,
|
|
741
|
-
mergeModel: `merge${capRefName}`,
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
723
|
|
|
745
724
|
static #helpersOf(signal: SerializedSignal) {
|
|
746
725
|
const get = !!FetchClient.#mountedGuards(signal, "get");
|
|
@@ -749,24 +728,13 @@ export class FetchClient {
|
|
|
749
728
|
update || !!FetchClient.#mountedGuards(signal, "create") || !!FetchClient.#mountedGuards(signal, "remove");
|
|
750
729
|
return { view: get, edit: get && write, merge: update };
|
|
751
730
|
}
|
|
752
|
-
static #sliceNames(refName: string, suffix: string) {
|
|
753
|
-
const capSuffix = capitalize(suffix);
|
|
754
|
-
const capRefName = capitalize(refName);
|
|
755
|
-
return {
|
|
756
|
-
list: `${refName}List${capSuffix}`,
|
|
757
|
-
insight: `${refName}Insight${capSuffix}`,
|
|
758
|
-
live: `${refName}Live${capSuffix}`,
|
|
759
|
-
init: `init${capRefName}${capSuffix}`,
|
|
760
|
-
getInit: `get${capRefName}Init${capSuffix}`,
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
731
|
/** The `fetch.*` names a module's signal registers, and the bare socket route of each pubsub and message. */
|
|
764
732
|
static claimsOf(refName: string, signal: SerializedSignal) {
|
|
765
733
|
const handlers: FetchClaim[] = [];
|
|
766
734
|
const sockets: FetchClaim[] = [];
|
|
767
735
|
const claim = (key: string, endpoint: SerializedEndpoint, by: string) => {
|
|
768
|
-
handlers.push({ key: endpoint.type === "pubsub" ?
|
|
769
|
-
if (endpoint.type === "message") handlers.push({ key:
|
|
736
|
+
handlers.push({ key: endpoint.type === "pubsub" ? endpointNames(key).subscribe : key, by });
|
|
737
|
+
if (endpoint.type === "message") handlers.push({ key: endpointNames(key).listen, by });
|
|
770
738
|
if (endpoint.type === "pubsub" || endpoint.type === "message") sockets.push({ key, by });
|
|
771
739
|
};
|
|
772
740
|
for (const [key, endpoint] of Object.entries(signal.endpoint))
|
|
@@ -775,7 +743,7 @@ export class FetchClient {
|
|
|
775
743
|
const generated = "the generated CRUD";
|
|
776
744
|
for (const [key, endpoint] of Object.entries(FetchClient.getBaseEndpoint(refName, signal)))
|
|
777
745
|
claim(key, endpoint, generated);
|
|
778
|
-
const names =
|
|
746
|
+
const names = modelNames(refName);
|
|
779
747
|
const helpers = FetchClient.#helpersOf(signal);
|
|
780
748
|
const helperKeys = [
|
|
781
749
|
...(helpers.view ? [names.viewModel, names.getModelView] : []),
|
|
@@ -788,13 +756,13 @@ export class FetchClient {
|
|
|
788
756
|
const by = suffix ? `the slice "${suffix}"` : "the root slice";
|
|
789
757
|
for (const [key, endpoint] of Object.entries(FetchClient.getEndpointFromSlice(refName, suffix, slice)))
|
|
790
758
|
claim(key, endpoint, by);
|
|
791
|
-
const { init, getInit } =
|
|
759
|
+
const { init, getInit } = sliceNames(refName, suffix).endpoint;
|
|
792
760
|
handlers.push({ key: init, by }, { key: getInit, by });
|
|
793
761
|
}
|
|
794
762
|
return { handlers, sockets };
|
|
795
763
|
}
|
|
796
764
|
#registerModelBaseEndpoint(refName: string, signal: SerializedSignal) {
|
|
797
|
-
const names =
|
|
765
|
+
const names = modelNames(refName);
|
|
798
766
|
|
|
799
767
|
for (const key of Object.values(names).filter((key) => this.#generatedBy.get(key) === refName)) {
|
|
800
768
|
this.#handlerFactory.delete(key);
|
|
@@ -810,7 +778,7 @@ export class FetchClient {
|
|
|
810
778
|
if (helpers.view) {
|
|
811
779
|
this.#setHandlerFactory(
|
|
812
780
|
names.viewModel,
|
|
813
|
-
() => this.#makeModelHandleFn(refName, names.model, names.viewModel,
|
|
781
|
+
() => this.#makeModelHandleFn(refName, names.model, names.viewModel, names.modelView),
|
|
814
782
|
refName,
|
|
815
783
|
);
|
|
816
784
|
this.#setHandlerFactory(names.getModelView, () => this.#makeModelObjFn(refName, names.getModelView), refName);
|
|
@@ -818,7 +786,7 @@ export class FetchClient {
|
|
|
818
786
|
if (helpers.edit) {
|
|
819
787
|
this.#setHandlerFactory(
|
|
820
788
|
names.editModel,
|
|
821
|
-
() => this.#makeModelHandleFn(refName, names.model, names.editModel,
|
|
789
|
+
() => this.#makeModelHandleFn(refName, names.model, names.editModel, names.modelEdit),
|
|
822
790
|
refName,
|
|
823
791
|
);
|
|
824
792
|
this.#setHandlerFactory(names.getModelEdit, () => this.#makeModelObjFn(refName, names.getModelEdit), refName);
|
|
@@ -867,13 +835,15 @@ export class FetchClient {
|
|
|
867
835
|
}
|
|
868
836
|
|
|
869
837
|
#makeModelObjFn(refName: string, callerKey: string) {
|
|
838
|
+
const names = modelNames(refName);
|
|
870
839
|
return (async (id: string, option?: FetchPolicy) => {
|
|
871
840
|
const modelObj = await this.#requireHandler(refName, callerKey)(id, { ...option, crystalize: false });
|
|
872
|
-
return { refName, [
|
|
841
|
+
return { refName, [names.modelObj]: modelObj, [names.modelViewAt]: new Date() };
|
|
873
842
|
}) as FetchHandler;
|
|
874
843
|
}
|
|
875
844
|
|
|
876
845
|
#makeModelHandleFn(refName: string, modelKey: string, callerKey: string, payloadKey: string) {
|
|
846
|
+
const names = modelNames(refName);
|
|
877
847
|
return ((id: string, option?: FetchPolicy) => {
|
|
878
848
|
const cnst = ConstantRegistry.getDatabase(refName);
|
|
879
849
|
const modelFn = this.#requireHandler(modelKey, callerKey);
|
|
@@ -882,7 +852,7 @@ export class FetchClient {
|
|
|
882
852
|
let payload: object | undefined;
|
|
883
853
|
const modelOf = (modelObj: object) => (model ??= new cnst.full(modelObj));
|
|
884
854
|
const payloadOf = (modelObj: object) =>
|
|
885
|
-
(payload ??= { refName, [
|
|
855
|
+
(payload ??= { refName, [names.modelObj]: modelObj, [names.modelViewAt]: new Date() });
|
|
886
856
|
return FetchHandle.of<Record<string, unknown>, Record<string, Promise<unknown>>>(
|
|
887
857
|
[modelRequest],
|
|
888
858
|
async () => {
|
|
@@ -902,7 +872,7 @@ export class FetchClient {
|
|
|
902
872
|
suffix: string,
|
|
903
873
|
slice: SerializedSlice,
|
|
904
874
|
): { [key: string]: SerializedEndpoint } {
|
|
905
|
-
const names =
|
|
875
|
+
const names = sliceNames(refName, suffix).endpoint;
|
|
906
876
|
|
|
907
877
|
const mcp = {
|
|
908
878
|
...(slice.mcp === false ? { mcp: false as const } : {}),
|
|
@@ -936,10 +906,7 @@ export class FetchClient {
|
|
|
936
906
|
return endpoint;
|
|
937
907
|
}
|
|
938
908
|
#registerSlice(refName: string, suffix: string, slice: SerializedSlice, prefix?: string) {
|
|
939
|
-
const
|
|
940
|
-
const sliceName = `${refName}${capSuffix}`;
|
|
941
|
-
const capRefName = capitalize(refName);
|
|
942
|
-
const names = FetchClient.#sliceNames(refName, suffix);
|
|
909
|
+
const { slice: sliceName, endpoint: names } = sliceNames(refName, suffix);
|
|
943
910
|
|
|
944
911
|
const endpoint = FetchClient.getEndpointFromSlice(refName, suffix, slice);
|
|
945
912
|
|
|
@@ -971,8 +938,7 @@ export class FetchClient {
|
|
|
971
938
|
) as Promise<BaseInsight | null>;
|
|
972
939
|
return new SliceInitHandle({
|
|
973
940
|
refName,
|
|
974
|
-
|
|
975
|
-
capSuffix,
|
|
941
|
+
suffix,
|
|
976
942
|
sliceName,
|
|
977
943
|
argLength,
|
|
978
944
|
queryArgs,
|
|
@@ -990,7 +956,7 @@ export class FetchClient {
|
|
|
990
956
|
names.init,
|
|
991
957
|
names.getInit,
|
|
992
958
|
);
|
|
993
|
-
return await initFn(...args)[
|
|
959
|
+
return await initFn(...args)[names.initResult];
|
|
994
960
|
});
|
|
995
961
|
}
|
|
996
962
|
#makeArgSerializer(args: SerializedArg[]) {
|
|
@@ -1117,7 +1083,7 @@ export class FetchClient {
|
|
|
1117
1083
|
const cnst = ConstantRegistry.getDatabase(refName, { allowEmpty: true });
|
|
1118
1084
|
if (!cnst) return;
|
|
1119
1085
|
const slices = Object.entries(serializedSignal.slice).map(([suffix, serializedSlice]) => {
|
|
1120
|
-
const sliceName =
|
|
1086
|
+
const { slice: sliceName } = sliceNames(refName, suffix);
|
|
1121
1087
|
proxy.slice[sliceName] = { refName, sliceName, argLength: serializedSlice.args.length };
|
|
1122
1088
|
return { refName, sliceName, serializedSlice };
|
|
1123
1089
|
});
|
|
@@ -25,6 +25,7 @@ const jsonContentType = /^application\/(?:[\w.+-]+\+)?json\b/i;
|
|
|
25
25
|
|
|
26
26
|
const transportErrorKeyMap = {
|
|
27
27
|
408: "base.error.gatewayTimeout",
|
|
28
|
+
413: "base.error.payloadTooLarge",
|
|
28
29
|
429: "base.error.tooManyRequests",
|
|
29
30
|
502: "base.error.serverUnavailable",
|
|
30
31
|
503: "base.error.serverUnavailable",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { DataList } from "akanjs/base";
|
|
2
|
+
import { modelNames, sliceNames } from "akanjs/common";
|
|
2
3
|
import { type BaseInsight, type BaseObject, withSharedInstances } from "akanjs/constant";
|
|
3
4
|
|
|
4
5
|
import { FetchHandle } from "./fetchHandle";
|
|
5
6
|
|
|
6
7
|
export interface SliceInitInput {
|
|
7
8
|
refName: string;
|
|
8
|
-
|
|
9
|
-
capSuffix: string;
|
|
9
|
+
suffix: string;
|
|
10
10
|
sliceName: string;
|
|
11
11
|
argLength: number;
|
|
12
12
|
queryArgs: unknown[];
|
|
@@ -35,12 +35,9 @@ export class SliceInitHandle {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
build() {
|
|
38
|
-
const { refName,
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
list: `${refName}List${capSuffix}`,
|
|
42
|
-
insight: `${refName}Insight${capSuffix}`,
|
|
43
|
-
};
|
|
38
|
+
const { refName, suffix, listRequest, insightRequest } = this.#input;
|
|
39
|
+
const { initResult, list, insight } = sliceNames(refName, suffix).endpoint;
|
|
40
|
+
const keys = { init: initResult, list, insight };
|
|
44
41
|
return FetchHandle.of<Record<string, unknown>, Record<string, Promise<unknown>>>(
|
|
45
42
|
[listRequest, insightRequest],
|
|
46
43
|
async () => {
|
|
@@ -62,23 +59,26 @@ export class SliceInitHandle {
|
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
#serverInitOf(modelObjList: BaseObject[], modelObjInsight: BaseInsight | null) {
|
|
65
|
-
const { refName,
|
|
62
|
+
const { refName, sliceName, argLength, queryArgs, page, limit, sort } = this.#input;
|
|
63
|
+
const { modelObjList: modelObjListKey, modelObjInsight: modelObjInsightKey } = modelNames(refName);
|
|
64
|
+
const { pageOfModel, lastPageOfModel, limitOfModel, hasMoreOfModel, queryArgsOfModel, sortOfModel, modelInitAt } =
|
|
65
|
+
sliceNames(refName).state;
|
|
66
66
|
const lastPage =
|
|
67
67
|
modelObjInsight?.count && limit > 0 ? Math.max(Math.floor((modelObjInsight.count - 1) / limit) + 1, 1) : 1;
|
|
68
68
|
this.#serverInit ??= {
|
|
69
69
|
refName,
|
|
70
70
|
sliceName,
|
|
71
71
|
argLength,
|
|
72
|
-
[
|
|
73
|
-
[
|
|
74
|
-
[
|
|
75
|
-
[
|
|
76
|
-
[
|
|
72
|
+
[modelObjListKey]: modelObjList,
|
|
73
|
+
[modelObjInsightKey]: modelObjInsight,
|
|
74
|
+
[pageOfModel]: page,
|
|
75
|
+
[lastPageOfModel]: lastPage,
|
|
76
|
+
[limitOfModel]: limit,
|
|
77
77
|
|
|
78
|
-
[
|
|
79
|
-
[
|
|
80
|
-
[
|
|
81
|
-
[
|
|
78
|
+
[hasMoreOfModel]: !!limit && modelObjList.length >= limit,
|
|
79
|
+
[queryArgsOfModel]: queryArgs,
|
|
80
|
+
[sortOfModel]: sort,
|
|
81
|
+
[modelInitAt]: new Date(),
|
|
82
82
|
};
|
|
83
83
|
return this.#serverInit;
|
|
84
84
|
}
|
package/fetch/index.ts
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getEnv } from "akanjs/base";
|
|
2
|
+
import { authTokenKey } from "akanjs/common";
|
|
3
|
+
|
|
4
|
+
interface SessionCookieIssueOption {
|
|
5
|
+
/** Seconds the pair is kept; left out, both are session cookies. */
|
|
6
|
+
maxAge?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class SessionCookie {
|
|
10
|
+
static claimsKey(): string {
|
|
11
|
+
return `${authTokenKey()}.claims`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static claimsOf(jwt: string): string {
|
|
15
|
+
const [header = "", payload = ""] = jwt.split(".");
|
|
16
|
+
return `${header}.${payload}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static issue(jwt: string, { maxAge }: SessionCookieIssueOption = {}): string[] {
|
|
20
|
+
const age = maxAge === undefined ? "" : `; Max-Age=${maxAge}`;
|
|
21
|
+
return [
|
|
22
|
+
`${authTokenKey()}=${encodeURIComponent(jwt)}; ${SessionCookie.#attrs()}; HttpOnly${age}`,
|
|
23
|
+
`${SessionCookie.claimsKey()}=${encodeURIComponent(SessionCookie.claimsOf(jwt))}; ${SessionCookie.#attrs()}${age}`,
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static expire(): string[] {
|
|
28
|
+
return [
|
|
29
|
+
`${authTokenKey()}=; ${SessionCookie.#attrs()}; HttpOnly; Max-Age=0`,
|
|
30
|
+
`${SessionCookie.claimsKey()}=; ${SessionCookie.#attrs()}; Max-Age=0`,
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static #attrs() {
|
|
35
|
+
return `Path=/; SameSite=Lax${getEnv().operationMode === "local" ? "" : "; Secure"}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/index.ts
CHANGED
|
@@ -163,6 +163,10 @@ export interface AkanNativeUpdatesConfig {
|
|
|
163
163
|
* phone always takes a new web bundle by itself. Default true.
|
|
164
164
|
*/
|
|
165
165
|
automatic?: boolean;
|
|
166
|
+
/** Windows: SHA-1 thumbprints a release must be Authenticode-signed by, in place of the installed app's signer. */
|
|
167
|
+
windowsSigners?: string[];
|
|
168
|
+
/** Seconds a published manifest stays valid, at least 3600; left out, manifests never expire. */
|
|
169
|
+
expiresIn?: number;
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
export interface AkanNativeIosConfig {
|
|
@@ -328,6 +332,10 @@ export interface AkanNativeSettings {
|
|
|
328
332
|
* internal test target only, since anyone holding the device can then read and drive the page.
|
|
329
333
|
*/
|
|
330
334
|
devtools?: boolean;
|
|
335
|
+
/** Directives extend the default policy, a string replaces it (`"strict"` is the native preset), false ships none. */
|
|
336
|
+
csp?: false | string | Record<string, string | string[]>;
|
|
337
|
+
/** The URL patterns `opener` may open. Default https, `mailto:` and `tel:` links; `["*"]` lifts the limit. */
|
|
338
|
+
openUrls?: string[];
|
|
331
339
|
ios?: AkanNativeIosConfig;
|
|
332
340
|
android?: AkanNativeAndroidConfig;
|
|
333
341
|
desktop?: AkanNativeDesktopConfig;
|