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
|
@@ -1,58 +1,76 @@
|
|
|
1
1
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { bearerTokenOf, Logger } from "akanjs/common";
|
|
3
|
+
import { isLocalDevRuntime } from "../../signal/trace";
|
|
4
|
+
import { FailureLimiter } from "./failureLimiter";
|
|
3
5
|
|
|
4
6
|
interface StatusAccessOptions {
|
|
5
7
|
tokens: (string | null | undefined)[];
|
|
6
8
|
open?: boolean;
|
|
9
|
+
failuresPerMinute?: number;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
export class StatusAccess {
|
|
13
|
+
static readonly minTokenLength = 32;
|
|
10
14
|
static readonly #noStore = { "cache-control": "no-store" };
|
|
11
15
|
static readonly #warned = new Set<string>();
|
|
12
16
|
readonly #digests: Buffer[];
|
|
13
17
|
readonly #open: boolean;
|
|
18
|
+
readonly #failures: FailureLimiter;
|
|
14
19
|
|
|
15
|
-
constructor({ tokens, open = false }: StatusAccessOptions) {
|
|
16
|
-
this.#digests = tokens.filter((token): token is string => !!token).map((token) => StatusAccess
|
|
20
|
+
constructor({ tokens, open = false, failuresPerMinute = 10 }: StatusAccessOptions) {
|
|
21
|
+
this.#digests = tokens.filter((token): token is string => !!token).map((token) => StatusAccess.digest(token));
|
|
17
22
|
this.#open = open;
|
|
23
|
+
this.#failures = new FailureLimiter({ limit: failuresPerMinute });
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
static fromEnv(): StatusAccess {
|
|
21
27
|
return new StatusAccess({
|
|
22
|
-
tokens: [StatusAccess
|
|
23
|
-
open:
|
|
28
|
+
tokens: [StatusAccess.tokenOf("AKAN_STATUS_TOKEN"), StatusAccess.tokenOf("AKAN_LOG_STREAM_TOKEN")],
|
|
29
|
+
open: isLocalDevRuntime(),
|
|
24
30
|
});
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
static
|
|
33
|
+
static tokenOf(name: string): string | null {
|
|
28
34
|
const token = process.env[name]?.trim();
|
|
29
35
|
if (!token) return null;
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
if (/\s/.test(token)) {
|
|
37
|
+
StatusAccess.#warnOnce(`${name} must be one word, with no whitespace; it unlocks nothing`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (token.length < StatusAccess.minTokenLength)
|
|
41
|
+
StatusAccess.#warnOnce(
|
|
42
|
+
`${name} is shorter than ${StatusAccess.minTokenLength} characters, short enough to guess; use a random one`,
|
|
43
|
+
);
|
|
44
|
+
return token;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
|
-
static #
|
|
47
|
+
static #warnOnce(message: string) {
|
|
48
|
+
if (StatusAccess.#warned.has(message)) return;
|
|
49
|
+
StatusAccess.#warned.add(message);
|
|
50
|
+
new Logger("StatusAccess").warn(message);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static digest(token: string): Buffer {
|
|
38
54
|
return createHash("sha256").update(token).digest();
|
|
39
55
|
}
|
|
40
56
|
|
|
41
|
-
allows(req: Request): boolean {
|
|
57
|
+
allows(req: Request, client?: string | null): boolean {
|
|
42
58
|
if (this.#open) return true;
|
|
43
59
|
const token = bearerTokenOf(req.headers.get("authorization"));
|
|
44
|
-
if (!token) return false;
|
|
45
|
-
const presented = StatusAccess
|
|
46
|
-
|
|
60
|
+
if (!token || this.#failures.blocked(client)) return false;
|
|
61
|
+
const presented = StatusAccess.digest(token);
|
|
62
|
+
const matched = this.#digests.reduce((matched, digest) => timingSafeEqual(presented, digest) || matched, false);
|
|
63
|
+
if (!matched) this.#failures.fail(client);
|
|
64
|
+
return matched;
|
|
47
65
|
}
|
|
48
66
|
|
|
49
|
-
health(req: Request, detail: () => { status: string }): Response {
|
|
67
|
+
health(req: Request, detail: () => { status: string }, client?: string | null): Response {
|
|
50
68
|
const body = detail();
|
|
51
|
-
return Response.json(this.allows(req) ? body : { status: body.status }, { headers: StatusAccess.#noStore });
|
|
69
|
+
return Response.json(this.allows(req, client) ? body : { status: body.status }, { headers: StatusAccess.#noStore });
|
|
52
70
|
}
|
|
53
71
|
|
|
54
|
-
metrics(req: Request, detail: () => unknown): Response {
|
|
55
|
-
if (!this.allows(req))
|
|
72
|
+
metrics(req: Request, detail: () => unknown, client?: string | null): Response {
|
|
73
|
+
if (!this.allows(req, client))
|
|
56
74
|
return new Response("Unauthorized", { status: 401, headers: { "www-authenticate": "Bearer" } });
|
|
57
75
|
return Response.json(detail(), { headers: StatusAccess.#noStore });
|
|
58
76
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getEnv } from "akanjs/base";
|
|
4
|
-
import { Logger, resolveSubRouteHosts } from "akanjs/common";
|
|
4
|
+
import { LocalePath, Logger, parseAkanI18nEnv, resolveSubRouteHosts } from "akanjs/common";
|
|
5
5
|
import type { BaseBuildArtifact } from "../types";
|
|
6
6
|
import { AkanResponse, type WebProxy } from "./types";
|
|
7
7
|
|
|
@@ -29,11 +29,8 @@ export class HostBasePathWebProxy implements WebProxy {
|
|
|
29
29
|
|
|
30
30
|
if (firstPath === basePath) {
|
|
31
31
|
if (isInternalProxyRequest(requestUrl)) return AkanResponse.next({ request: { headers } });
|
|
32
|
-
const publicPath =
|
|
33
|
-
return Response.redirect(
|
|
34
|
-
new URL(`${publicPath || `/${locale}`}${targetUrl.search}`, getPublicRequestUrl(request)),
|
|
35
|
-
308,
|
|
36
|
-
);
|
|
32
|
+
const publicPath = LocalePath.join(locale, `/${segments.slice(3).filter(Boolean).join("/")}`, parseAkanI18nEnv());
|
|
33
|
+
return Response.redirect(new URL(`${publicPath}${targetUrl.search}`, getPublicRequestUrl(request)), 308);
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
const internalPath = `/${[locale, basePath, ...segments.slice(2)].filter(Boolean).join("/")}`;
|
|
@@ -114,7 +111,7 @@ function getProxyTargetUrl(requestUrl: URL): URL {
|
|
|
114
111
|
return target ? new URL(target, requestUrl.origin) : new URL(requestUrl);
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
function toProxyRequestUrl(requestUrl: URL, targetUrl: URL): URL {
|
|
114
|
+
export function toProxyRequestUrl(requestUrl: URL, targetUrl: URL): URL {
|
|
118
115
|
if (!isInternalProxyRequest(requestUrl)) return targetUrl;
|
|
119
116
|
const rewritten = new URL(requestUrl);
|
|
120
117
|
rewritten.searchParams.set("url", `${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getApiPrefix } from "akanjs/base";
|
|
2
|
-
import { parseAkanI18nEnv } from "akanjs/common";
|
|
3
|
-
import { getPublicRequestUrl, isInternalProxyRequest } from "./hostBasePathWebProxy";
|
|
2
|
+
import { LocalePath, parseAkanI18nEnv } from "akanjs/common";
|
|
3
|
+
import { getPublicRequestUrl, isInternalProxyRequest, toProxyRequestUrl } from "./hostBasePathWebProxy";
|
|
4
4
|
import { AkanResponse, type WebProxy } from "./types";
|
|
5
5
|
|
|
6
6
|
function getLocale(request: Bun.BunRequest): string {
|
|
@@ -46,39 +46,59 @@ export class LocaleWebProxy implements WebProxy {
|
|
|
46
46
|
|
|
47
47
|
const targetUrl = getProxyTargetUrl(requestUrl);
|
|
48
48
|
const pathname = targetUrl.pathname;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
);
|
|
49
|
+
const isInternal = isInternalProxyRequest(requestUrl);
|
|
50
|
+
const isPage = !isInternal && !isWellKnownRequest(pathname) && !isApiRequest(pathname);
|
|
51
|
+
const { locale, path, isPrefixed } = LocalePath.split(pathname, i18n);
|
|
52
52
|
|
|
53
|
-
if (
|
|
54
|
-
!isInternalProxyRequest(requestUrl) &&
|
|
55
|
-
!isWellKnownRequest(pathname) &&
|
|
56
|
-
!isApiRequest(pathname) &&
|
|
57
|
-
pathnameIsMissingLocale
|
|
58
|
-
) {
|
|
53
|
+
if (isPage && !locale) {
|
|
59
54
|
return Response.redirect(
|
|
60
55
|
new URL(`/${getLocale(request)}/${pathname.slice(1)}${targetUrl.search}`, getPublicRequestUrl(request)),
|
|
61
56
|
307,
|
|
62
57
|
);
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
if (isPage && locale && LocalePath.omits(locale, i18n)) {
|
|
61
|
+
const publicUrl = getPublicRequestUrl(request);
|
|
62
|
+
if (isCsrRequest(targetUrl) && !isPrefixed)
|
|
63
|
+
return Response.redirect(onOrigin(publicUrl, `/${locale}${pathname}`, targetUrl.search), 307);
|
|
64
|
+
|
|
65
|
+
if (!isCsrRequest(targetUrl) && isPrefixed)
|
|
66
|
+
return new Response(null, {
|
|
67
|
+
status: 308,
|
|
68
|
+
headers: { location: onOrigin(publicUrl, path, targetUrl.search).href, "cache-control": "max-age=86400" },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
68
71
|
|
|
69
|
-
headers
|
|
70
|
-
headers.set("x-
|
|
72
|
+
const headers = new Headers(request.headers);
|
|
73
|
+
headers.set("x-locale", locale ?? i18n.defaultLocale);
|
|
74
|
+
headers.set("x-path", path);
|
|
71
75
|
|
|
76
|
+
if (locale && !isPrefixed && (isPage || isInternal)) {
|
|
77
|
+
const localizedUrl = new URL(targetUrl);
|
|
78
|
+
localizedUrl.pathname = `/${locale}${pathname}`;
|
|
79
|
+
return AkanResponse.rewrite(toProxyRequestUrl(requestUrl, localizedUrl), { request: { headers } });
|
|
80
|
+
}
|
|
72
81
|
return AkanResponse.next({ request: { headers } });
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
84
|
|
|
85
|
+
function onOrigin(publicUrl: URL, pathname: string, search: string): URL {
|
|
86
|
+
const location = new URL(publicUrl);
|
|
87
|
+
location.pathname = pathname;
|
|
88
|
+
location.search = search;
|
|
89
|
+
return location;
|
|
90
|
+
}
|
|
91
|
+
|
|
76
92
|
function getProxyTargetUrl(requestUrl: URL): URL {
|
|
77
93
|
if (!isInternalProxyRequest(requestUrl)) return requestUrl;
|
|
78
94
|
const target = requestUrl.searchParams.get("url");
|
|
79
95
|
return target ? new URL(target, requestUrl.origin) : requestUrl;
|
|
80
96
|
}
|
|
81
97
|
|
|
98
|
+
function isCsrRequest(url: URL): boolean {
|
|
99
|
+
return url.searchParams.get("csr") === "true";
|
|
100
|
+
}
|
|
101
|
+
|
|
82
102
|
function isWellKnownRequest(pathname: string): boolean {
|
|
83
103
|
return pathname === "/.well-known" || pathname.startsWith("/.well-known/");
|
|
84
104
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PromiseOrObject } from "akanjs/base";
|
|
2
|
-
import { applyMixins, capitalize } from "akanjs/common";
|
|
2
|
+
import { applyMixins, capitalize, modelNames } from "akanjs/common";
|
|
3
3
|
import { type ConstantModel, type QueryOf, resolvePageLimit, resolvePageSkip } from "akanjs/constant";
|
|
4
4
|
import {
|
|
5
5
|
assertFilterFitsCrud,
|
|
@@ -65,6 +65,7 @@ export class DatabaseResolver {
|
|
|
65
65
|
database: DatabaseModel,
|
|
66
66
|
): { adaptor: AdaptorCls<DatabaseInstance>; schema: DocumentSchema } {
|
|
67
67
|
const [modelName, className]: [string, string] = [database.refName, capitalize(database.refName)];
|
|
68
|
+
const names = modelNames(modelName);
|
|
68
69
|
|
|
69
70
|
const resolveSort = (sortKey?: string | null): { [key: string]: 1 | -1 } | null => {
|
|
70
71
|
if (!sortKey) return null;
|
|
@@ -133,10 +134,10 @@ export class DatabaseResolver {
|
|
|
133
134
|
const listenPreHook = listen("pre");
|
|
134
135
|
const listenPostHook = listen("post");
|
|
135
136
|
|
|
136
|
-
class DatabaseModelInstance extends adapt(
|
|
137
|
+
class DatabaseModelInstance extends adapt(names.modelModel, ({ plug }) => ({
|
|
137
138
|
__database: plug(DatabaseAdaptorRole, (database) => database),
|
|
138
139
|
__cache: plug(CacheAdaptorRole, (cache) => new CacheDatabase(modelName, cache)),
|
|
139
|
-
[
|
|
140
|
+
[names.modelCache as never]: plug(CacheAdaptorRole, (cache) => new CacheDatabase(modelName, cache)),
|
|
140
141
|
})) {
|
|
141
142
|
declare readonly __database: DatabaseAdaptor;
|
|
142
143
|
__store!: DocumentStore;
|
|
@@ -154,11 +155,11 @@ export class DatabaseResolver {
|
|
|
154
155
|
const byId = new Map(docs.map((doc) => [String(doc.id), doc]));
|
|
155
156
|
return ids.map((id) => byId.get(String(id)) ?? null);
|
|
156
157
|
},
|
|
157
|
-
{ name:
|
|
158
|
+
{ name: names.modelLoader, cache: false },
|
|
158
159
|
);
|
|
159
160
|
Object.assign(this, {
|
|
160
161
|
[className]: this.__model,
|
|
161
|
-
[
|
|
162
|
+
[names.modelLoader]: this.__loader,
|
|
162
163
|
});
|
|
163
164
|
Object.entries(getLoaderInfos(database.model)).forEach(([key, loaderInfo]) => {
|
|
164
165
|
Object.assign(this, {
|
|
@@ -373,19 +374,19 @@ export class DatabaseResolver {
|
|
|
373
374
|
if (!doc) throw new NoDocumentError(`No Document (${database.refName}): ${id}`);
|
|
374
375
|
return doc;
|
|
375
376
|
}
|
|
376
|
-
async [
|
|
377
|
+
async [names.getModel](id: string) {
|
|
377
378
|
return this.__get(id);
|
|
378
379
|
}
|
|
379
380
|
async __load(id?: string) {
|
|
380
381
|
return (id ? await this.__loader.load(id) : null) as unknown;
|
|
381
382
|
}
|
|
382
|
-
async [
|
|
383
|
+
async [names.loadModel](id?: string) {
|
|
383
384
|
return this.__load(id);
|
|
384
385
|
}
|
|
385
386
|
async __loadMany(ids: string[]) {
|
|
386
387
|
return await this.__loader.loadMany(ids);
|
|
387
388
|
}
|
|
388
|
-
async [
|
|
389
|
+
async [names.loadModelMany](ids: string[]) {
|
|
389
390
|
return this.__loadMany(ids);
|
|
390
391
|
}
|
|
391
392
|
async clone(data: DataInputOf<any, any> & { id: string }) {
|
|
@@ -394,13 +395,16 @@ export class DatabaseResolver {
|
|
|
394
395
|
async __create(data: DataInputOf<any, any>) {
|
|
395
396
|
return await timedQuery(() => this.__store.create(data));
|
|
396
397
|
}
|
|
397
|
-
async [
|
|
398
|
+
async [names.createModel](data: DataInputOf<any, any>) {
|
|
398
399
|
return this.__create(data);
|
|
399
400
|
}
|
|
401
|
+
async __createMany(data: DataInputOf<any, any>[]) {
|
|
402
|
+
return await timedQuery(() => this.__store.createMany(data));
|
|
403
|
+
}
|
|
400
404
|
async __update(id: string, data: DataInputOf<any, any>) {
|
|
401
405
|
return await timedQuery(() => this.__store.update(id, data));
|
|
402
406
|
}
|
|
403
|
-
async [
|
|
407
|
+
async [names.updateModel](id: string, data: DataInputOf<any, any>) {
|
|
404
408
|
return this.__update(id, data);
|
|
405
409
|
}
|
|
406
410
|
async __remove(id: string) {
|
|
@@ -421,7 +425,7 @@ export class DatabaseResolver {
|
|
|
421
425
|
async __updateOne(query: QueryOf<any>, update: DocumentUpdateInput, options?: DocumentTouchOptions) {
|
|
422
426
|
return await timedQuery(() => this.__store.updateOneByQuery(query, update, ...touchArgs(options)));
|
|
423
427
|
}
|
|
424
|
-
async [
|
|
428
|
+
async [names.removeModel](id: string) {
|
|
425
429
|
return this.__remove(id);
|
|
426
430
|
}
|
|
427
431
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { capitalize } from "akanjs/common";
|
|
1
|
+
import { capitalize, modelNames } from "akanjs/common";
|
|
2
2
|
import type { QueryOf } from "akanjs/constant";
|
|
3
3
|
import type { DatabaseModel, DataInputOf, Doc, DocumentTouchOptions, DocumentUpdateInput } from "akanjs/document";
|
|
4
4
|
import { type DatabaseService, type ServiceCls, ServiceModel } from "akanjs/service";
|
|
@@ -17,6 +17,15 @@ export class ServiceResolver {
|
|
|
17
17
|
const doc = await this.__databaseModel.__create(input);
|
|
18
18
|
return await this.__libsPostCreate(doc);
|
|
19
19
|
},
|
|
20
|
+
async __createMany(this: DatabaseService, data: DataInputOf[]) {
|
|
21
|
+
if (!data.length) return [];
|
|
22
|
+
const inputs: DataInputOf[] = [];
|
|
23
|
+
for (const row of data) inputs.push(await this.__libsPreCreate(row));
|
|
24
|
+
const created: Doc[] = [];
|
|
25
|
+
for (const doc of await this.__databaseModel.__createMany(inputs))
|
|
26
|
+
created.push(await this.__libsPostCreate(doc));
|
|
27
|
+
return created;
|
|
28
|
+
},
|
|
20
29
|
async __update(this: DatabaseService, id: string, data: DataInputOf) {
|
|
21
30
|
const input = await this.__libsPreUpdate(id, data);
|
|
22
31
|
const doc = await this.__databaseModel.__update(id, input);
|
|
@@ -56,7 +65,13 @@ export class ServiceResolver {
|
|
|
56
65
|
}
|
|
57
66
|
static resolveDatabaseService(database: DatabaseModel, srvRef: ServiceCls, cascade: CascadeRunner): ServiceCls {
|
|
58
67
|
const className = capitalize(database.refName);
|
|
59
|
-
|
|
68
|
+
|
|
69
|
+
const createMany = modelNames(database.refName).createModelMany;
|
|
70
|
+
const methods = Object.entries(ServiceResolver.#getDefaultDbServiceMethods(database.refName, className, cascade));
|
|
71
|
+
Object.assign(
|
|
72
|
+
srvRef.prototype,
|
|
73
|
+
Object.fromEntries(methods.filter(([key]) => key !== createMany || !(key in srvRef.prototype))),
|
|
74
|
+
);
|
|
60
75
|
DatabaseResolver.applyFilterMethods(srvRef.prototype, database, className);
|
|
61
76
|
return srvRef;
|
|
62
77
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type PromiseOrObject,
|
|
14
14
|
SLICE_META,
|
|
15
15
|
} from "akanjs/base";
|
|
16
|
-
import { capitalize, Logger } from "akanjs/common";
|
|
16
|
+
import { capitalize, Logger, modelNames, moduleNames, sliceNames } from "akanjs/common";
|
|
17
17
|
import {
|
|
18
18
|
type ConstantField,
|
|
19
19
|
deserialize,
|
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
type WebsocketAdaptor,
|
|
36
36
|
WebsocketAdaptorRole,
|
|
37
37
|
} from "akanjs/service";
|
|
38
|
-
import { websocketRoomContract } from "../../common/websocketContract";
|
|
39
38
|
import { type Endpoint, type EndpointCls, sliceEndpoint } from "../../signal/endpoint";
|
|
40
39
|
import { EndpointErrorContract } from "../../signal/endpointErrorContract";
|
|
41
40
|
import type { EndpointInfo } from "../../signal/endpointInfo";
|
|
@@ -43,6 +42,7 @@ import type { Internal, InternalCls } from "../../signal/internal";
|
|
|
43
42
|
import { Machine } from "../../signal/internalArg";
|
|
44
43
|
import type { InternalInfo } from "../../signal/internalInfo";
|
|
45
44
|
import type { MiddlewareCls } from "../../signal/middleware";
|
|
45
|
+
import { RoomKey } from "../../signal/roomKey";
|
|
46
46
|
import type { ServerSignal, ServerSignalCls } from "../../signal/serverSignal";
|
|
47
47
|
import { SignalContext, type WebSocketExecutionContext } from "../../signal/signalContext";
|
|
48
48
|
import type { SliceCls } from "../../signal/slice";
|
|
@@ -70,9 +70,6 @@ type HttpMethodRoutes = Record<string, (req: Bun.BunRequest) => Response | Promi
|
|
|
70
70
|
export class SignalResolver {
|
|
71
71
|
static logger = new Logger("SignalResolver");
|
|
72
72
|
|
|
73
|
-
static makeRoomId(key: string, args: unknown[]) {
|
|
74
|
-
return websocketRoomContract.idOf(key, args);
|
|
75
|
-
}
|
|
76
73
|
static #localPublish: LocalPublish = () => {
|
|
77
74
|
SignalResolver.logger.verbose(`Local publish is not initialized yet`);
|
|
78
75
|
};
|
|
@@ -143,7 +140,7 @@ export class SignalResolver {
|
|
|
143
140
|
registry,
|
|
144
141
|
live,
|
|
145
142
|
});
|
|
146
|
-
const roomId =
|
|
143
|
+
const roomId = RoomKey.idOfPublished(key, endpointInfo.args, roomArgs);
|
|
147
144
|
if (isBinaryFrame) {
|
|
148
145
|
const bytes = Binary._parse(resolvedData as Uint8Array) as Uint8Array;
|
|
149
146
|
websocket.publish(roomId, bytes, { coalesce: SignalResolver.coalescesRoom(roomId) });
|
|
@@ -197,7 +194,7 @@ export class SignalResolver {
|
|
|
197
194
|
const refName = cnst.refName;
|
|
198
195
|
const liveKeys = Object.entries(sliceMeta)
|
|
199
196
|
.filter(([, sliceInfo]) => sliceInfo.liveOption && sliceInfo.execFn)
|
|
200
|
-
.map(([key]) =>
|
|
197
|
+
.map(([key]) => sliceNames(refName, key).endpoint.live);
|
|
201
198
|
if (!liveKeys.length) return [];
|
|
202
199
|
const service = live.service.get(refName) as
|
|
203
200
|
| {
|
|
@@ -387,8 +384,8 @@ export class SignalResolver {
|
|
|
387
384
|
const cnst = sliceCls.srv.cnst;
|
|
388
385
|
if (!cnst) throw new Error("Constant is not set for slice");
|
|
389
386
|
const refName = cnst.refName;
|
|
390
|
-
const serviceName =
|
|
391
|
-
const
|
|
387
|
+
const serviceName = moduleNames(refName).service;
|
|
388
|
+
const names = modelNames(refName);
|
|
392
389
|
|
|
393
390
|
const assertSliceQuery = (query: unknown, key: string) => {
|
|
394
391
|
if (Array.isArray(query))
|
|
@@ -404,10 +401,10 @@ export class SignalResolver {
|
|
|
404
401
|
const endpointObj: { [key: string]: EndpointInfo } = {};
|
|
405
402
|
Object.entries(sliceMeta).forEach(([key, sliceInfo]) => {
|
|
406
403
|
if (!sliceInfo.execFn) return;
|
|
407
|
-
const
|
|
404
|
+
const { slice: sliceName, endpoint: namesOfSlice } = sliceNames(refName, key);
|
|
408
405
|
const argLength = sliceInfo.args.length;
|
|
409
406
|
|
|
410
|
-
const listKey =
|
|
407
|
+
const listKey = namesOfSlice.list;
|
|
411
408
|
endpointObj[listKey] = (builder as any)
|
|
412
409
|
.query([sliceInfo.light], sliceInfo.signalOption)
|
|
413
410
|
._addArgs(sliceInfo.args)
|
|
@@ -418,11 +415,7 @@ export class SignalResolver {
|
|
|
418
415
|
.exec(async function (this: any, ...requestArgs: any) {
|
|
419
416
|
const args = requestArgs.slice(0, argLength);
|
|
420
417
|
const skip = resolvePageSkip(requestArgs[argLength]);
|
|
421
|
-
const limit = SignalResolver.#clientPageLimit(
|
|
422
|
-
listKey,
|
|
423
|
-
`${capitalizedRefName}${capitalizedKey}`,
|
|
424
|
-
requestArgs[argLength + 1],
|
|
425
|
-
);
|
|
418
|
+
const limit = SignalResolver.#clientPageLimit(listKey, capitalize(sliceName), requestArgs[argLength + 1]);
|
|
426
419
|
const sort = requestArgs[argLength + 2] ?? "latest";
|
|
427
420
|
const internalArgs = requestArgs.slice(argLength + 3);
|
|
428
421
|
const query = assertSliceQuery(
|
|
@@ -445,7 +438,7 @@ export class SignalResolver {
|
|
|
445
438
|
);
|
|
446
439
|
SignalResolver.#assertLiveSort(refName, key, sliceInfo);
|
|
447
440
|
SignalResolver.#assertLivePauseOn(refName, key, sliceInfo);
|
|
448
|
-
const liveKey =
|
|
441
|
+
const liveKey = namesOfSlice.live;
|
|
449
442
|
|
|
450
443
|
const liveBuilder = (builder as any).pubsub(Any, {
|
|
451
444
|
...sliceInfo.signalOption,
|
|
@@ -474,7 +467,7 @@ export class SignalResolver {
|
|
|
474
467
|
});
|
|
475
468
|
}
|
|
476
469
|
|
|
477
|
-
const insightKey =
|
|
470
|
+
const insightKey = namesOfSlice.insight;
|
|
478
471
|
endpointObj[insightKey] = (builder as any)
|
|
479
472
|
.query(sliceInfo.insight, sliceInfo.signalOption)
|
|
480
473
|
._addArgs(sliceInfo.args)
|
|
@@ -491,23 +484,23 @@ export class SignalResolver {
|
|
|
491
484
|
});
|
|
492
485
|
|
|
493
486
|
if (sliceCls.crud.get) {
|
|
494
|
-
endpointObj[
|
|
487
|
+
endpointObj[names.model] = (builder as any)
|
|
495
488
|
.query(cnst.full, { guards: sliceCls.getGuards })
|
|
496
|
-
.param(
|
|
489
|
+
.param(names.modelId, ID)
|
|
497
490
|
.exec(async function (this: any, id: string) {
|
|
498
|
-
return await this[serviceName][
|
|
491
|
+
return await this[serviceName][names.getModel](id);
|
|
499
492
|
});
|
|
500
493
|
|
|
501
|
-
endpointObj[
|
|
494
|
+
endpointObj[names.lightModel] = (builder as any)
|
|
502
495
|
.query(cnst.light, { guards: sliceCls.getGuards })
|
|
503
|
-
.param(
|
|
496
|
+
.param(names.modelId, ID)
|
|
504
497
|
.exec(async function (this: any, id: string) {
|
|
505
|
-
return await this[serviceName][
|
|
498
|
+
return await this[serviceName][names.getModel](id);
|
|
506
499
|
});
|
|
507
500
|
}
|
|
508
501
|
|
|
509
502
|
if (sliceCls.crud.create)
|
|
510
|
-
endpointObj[
|
|
503
|
+
endpointObj[names.createModel] = (builder as any)
|
|
511
504
|
.mutation(cnst.full, { guards: sliceCls.createGuards })
|
|
512
505
|
.body("data", cnst.input)
|
|
513
506
|
.exec(async function (this: any, data: any) {
|
|
@@ -515,18 +508,18 @@ export class SignalResolver {
|
|
|
515
508
|
});
|
|
516
509
|
|
|
517
510
|
if (sliceCls.crud.update)
|
|
518
|
-
endpointObj[
|
|
511
|
+
endpointObj[names.updateModel] = (builder as any)
|
|
519
512
|
.mutation(cnst.full, { guards: sliceCls.updateGuards })
|
|
520
|
-
.param(
|
|
513
|
+
.param(names.modelId, ID)
|
|
521
514
|
.body("data", cnst.input)
|
|
522
515
|
.exec(async function (this: any, id: string, data: any) {
|
|
523
516
|
return await this[serviceName].__update(id, data);
|
|
524
517
|
});
|
|
525
518
|
|
|
526
519
|
if (sliceCls.crud.remove)
|
|
527
|
-
endpointObj[
|
|
520
|
+
endpointObj[names.removeModel] = (builder as any)
|
|
528
521
|
.mutation(cnst.full, { guards: sliceCls.removeGuards })
|
|
529
|
-
.param(
|
|
522
|
+
.param(names.modelId, ID)
|
|
530
523
|
.exec(async function (this: any, id: string) {
|
|
531
524
|
return await this[serviceName].__remove(id);
|
|
532
525
|
});
|
|
@@ -658,7 +651,7 @@ export class SignalResolver {
|
|
|
658
651
|
const routeOptions: NonNullable<SignalRoutes["routeOptions"]> = {};
|
|
659
652
|
const wsRoutes: WebsocketRoutes = {};
|
|
660
653
|
const defaultPrefix = endpointCls.srv.cnst?.refName;
|
|
661
|
-
SignalResolver.#
|
|
654
|
+
SignalResolver.#checkUnguarded(endpointMeta);
|
|
662
655
|
SignalResolver.#warnMachineKeyless(endpointMeta);
|
|
663
656
|
SignalResolver.#warnUndefinedErrors(endpointMeta);
|
|
664
657
|
Object.entries(endpointMeta).forEach(([key, endpointInfo]) => {
|
|
@@ -711,11 +704,12 @@ export class SignalResolver {
|
|
|
711
704
|
|
|
712
705
|
const query = subscribe ? await context.exec() : null;
|
|
713
706
|
|
|
714
|
-
const requestRoomId = context.
|
|
707
|
+
const requestRoomId = context.getRequestRoomId(key);
|
|
708
|
+
const baseRoomId = context.getRoomId(key);
|
|
715
709
|
if (subscribe) {
|
|
716
710
|
const isClosed = SignalResolver.#closedWs.has(ws);
|
|
717
711
|
|
|
718
|
-
if (isClosed || SignalResolver.#pendingSubscribes.isLeft(ws,
|
|
712
|
+
if (isClosed || SignalResolver.#pendingSubscribes.isLeft(ws, baseRoomId, arrival)) {
|
|
719
713
|
await SignalResolver.#runLifecycleHandlers(
|
|
720
714
|
[context as SignalContext<WebSocketExecutionContext>],
|
|
721
715
|
isClosed ? ["unsubscribe", "disconnect"] : ["unsubscribe"],
|
|
@@ -729,7 +723,7 @@ export class SignalResolver {
|
|
|
729
723
|
};
|
|
730
724
|
return unsubscribed;
|
|
731
725
|
}
|
|
732
|
-
const roomId = liveOption ? context.getLiveRoomId(key) :
|
|
726
|
+
const roomId = liveOption ? context.getLiveRoomId(key, query) : baseRoomId;
|
|
733
727
|
const roomCtxMap = SignalResolver.#liveWsPubsubRoomCtx.get(ws) ?? new Map();
|
|
734
728
|
if (liveOption && !roomCtxMap.has(roomId))
|
|
735
729
|
live.syncHub.join({
|
|
@@ -763,10 +757,10 @@ export class SignalResolver {
|
|
|
763
757
|
};
|
|
764
758
|
return ack;
|
|
765
759
|
}
|
|
766
|
-
SignalResolver.#pendingSubscribes.leave(ws,
|
|
760
|
+
SignalResolver.#pendingSubscribes.leave(ws, baseRoomId, arrival);
|
|
767
761
|
|
|
768
762
|
const heldRooms = [...(SignalResolver.#liveWsPubsubRoomCtx.get(ws) ?? [])].filter(
|
|
769
|
-
([, roomCtx]) => roomCtx.getRoomId(roomCtx.key) ===
|
|
763
|
+
([, roomCtx]) => roomCtx.getRoomId(roomCtx.key) === baseRoomId,
|
|
770
764
|
);
|
|
771
765
|
for (const [roomId, roomCtx] of heldRooms) {
|
|
772
766
|
SignalResolver.#leaveWsRoom(ws, roomId, roomCtx, websocket, liveOption ? live : undefined);
|
|
@@ -807,15 +801,31 @@ export class SignalResolver {
|
|
|
807
801
|
return { routes, wsRoutes, routeOptions };
|
|
808
802
|
}
|
|
809
803
|
|
|
810
|
-
static #
|
|
811
|
-
const
|
|
804
|
+
static #checkUnguarded(endpointMeta: { [key: string]: EndpointInfo }) {
|
|
805
|
+
const unguarded = Object.entries(endpointMeta)
|
|
812
806
|
.filter(([, endpointInfo]) => !endpointInfo.signalOption.guards?.length)
|
|
813
807
|
.map(([key]) => key);
|
|
814
|
-
if (!
|
|
815
|
-
|
|
816
|
-
|
|
808
|
+
if (!unguarded.length) return;
|
|
809
|
+
const names = `${unguarded.join(", ")} declare${unguarded.length === 1 ? "s" : ""} no guards`;
|
|
810
|
+
const fix = "Name the guards, or `guards: [Public]` to open one on purpose.";
|
|
811
|
+
if (SignalContext.unguardedOpen()) {
|
|
812
|
+
SignalResolver.logger.warn(`${names}, and AKAN_UNGUARDED_ENDPOINTS=open lets anyone call them. ${fix}`);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (SignalResolver.#isLocalOperation()) {
|
|
816
|
+
SignalResolver.logger.warn(`${names}, so every call to them is refused. ${fix}`);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
throw new Error(
|
|
820
|
+
`${names}, so every call to them would be refused. ${fix} AKAN_UNGUARDED_ENDPOINTS=open answers them as before.`,
|
|
817
821
|
);
|
|
818
822
|
}
|
|
823
|
+
|
|
824
|
+
static #isLocalOperation() {
|
|
825
|
+
if (process.env.NODE_ENV === "test") return true;
|
|
826
|
+
const environment = process.env.AKAN_PUBLIC_ENV ?? "debug";
|
|
827
|
+
return (process.env.AKAN_PUBLIC_OPERATION_MODE ?? (environment === "local" ? "local" : "cloud")) === "local";
|
|
828
|
+
}
|
|
819
829
|
|
|
820
830
|
static #warnUndefinedErrors(endpointMeta: { [key: string]: EndpointInfo }) {
|
|
821
831
|
const declared = Object.entries(endpointMeta).flatMap(([key, endpointInfo]) =>
|