akanjs 3.0.0-alpha.99 → 3.0.0-beta.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/client/csrTypes.ts +3 -0
- package/client/index.ts +1 -0
- package/client/route/LayoutDefinition.ts +46 -0
- package/client/route/PageDefinition.ts +53 -0
- package/client/route/RootLayoutDefinition.ts +58 -0
- package/client/route/RouteDefinition.ts +204 -0
- package/client/route/index.ts +7 -0
- package/client/route/resolveRouteModule.ts +46 -0
- package/client/route/routeArgs.ts +51 -0
- package/client/route/routeBuilders.ts +10 -0
- package/common/TrustedProxy.ts +23 -1
- package/common/index.ts +0 -1
- package/common/mcpExposure.ts +5 -22
- package/constant/cascadePaths.ts +20 -20
- package/constant/deserialize.ts +30 -3
- package/constant/fieldInfo.ts +22 -16
- package/constant/purify.ts +2 -0
- package/constant/types.ts +6 -2
- package/dictionary/base.dictionary.ts +1 -0
- package/document/documentSchema.ts +20 -0
- package/document/filterMeta.ts +21 -0
- package/document/into.ts +0 -8
- package/fetch/client/fetchClient.ts +17 -8
- package/fetch/client/httpClient.ts +2 -30
- package/fetch/client/index.ts +1 -0
- package/fetch/client/remoteError.ts +57 -0
- package/fetch/client/wsClient.ts +2 -9
- package/fetch/requestStorage.ts +15 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/package.json +1 -1
- package/server/akanApp.ts +21 -1
- package/server/akanAppHeaders.ts +1 -1
- package/server/akanOption.ts +7 -6
- package/server/akanServer.ts +78 -7
- package/server/di/diLifecycle.ts +108 -7
- package/server/di/utils.ts +1 -1
- package/server/index.ts +1 -0
- package/server/mcp/McpAuth.ts +107 -29
- package/server/mcp/McpDispatcher.ts +6 -40
- package/server/mcp/McpExecutionContext.ts +6 -1
- package/server/mcp/McpRateLimiter.ts +88 -0
- package/server/mcp/McpRouter.ts +226 -50
- package/server/mcp/PagePromptComposer.ts +205 -0
- package/server/mcp/index.ts +1 -0
- package/server/oauth/OAuthAuthorize.ts +84 -0
- package/server/oauth/OAuthClientIdMetadata.ts +139 -0
- package/server/oauth/OAuthErrors.ts +77 -0
- package/server/oauth/OAuthMetadata.ts +59 -0
- package/server/oauth/OAuthPkce.ts +29 -0
- package/server/oauth/OAuthRedirect.ts +57 -0
- package/server/oauth/OAuthRegistration.ts +86 -0
- package/server/oauth/OAuthRevocation.ts +60 -0
- package/server/oauth/OAuthToken.ts +115 -0
- package/server/oauth/index.ts +10 -0
- package/server/oauth/oauthTypes.ts +79 -0
- package/server/resolver/CascadeRunner.ts +1 -1
- package/server/resolver/database.resolver.ts +70 -72
- package/server/resolver/service.resolver.ts +4 -14
- package/server/resolver/signal.resolver.ts +0 -8
- package/server/routeTreeBuilder.ts +45 -19
- package/server/routing/apiRouter.ts +1 -15
- package/server/rscPagePrompts.ts +175 -0
- package/server/rscWorker.tsx +41 -1
- package/server/rscWorkerHost.ts +54 -0
- package/server/webRouter.ts +9 -0
- package/service/agent.service.ts +31 -1
- package/service/predefinedAdaptor/deepseekLlm.ts +1 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +2 -0
- package/signal/endpointInfo.ts +2 -38
- package/signal/exception.ts +18 -0
- package/signal/guard.ts +14 -1
- package/signal/mcp/McpDocument.ts +51 -69
- package/signal/mcp/McpUriTemplate.ts +22 -0
- package/signal/mcp/index.ts +2 -1
- package/signal/mcp/mcpProtocol.ts +2 -0
- package/signal/mcp/pagePrompt.ts +45 -0
- package/signal/openapi/openapi.ts +4 -19
- package/signal/schema/JsonSchemaBuilder.ts +55 -18
- package/signal/serializer/fetch.serializer.ts +15 -0
- package/signal/signalContext.ts +49 -29
- package/signal/types.ts +8 -1
- package/store/action.ts +15 -12
- package/store/agent/AgentBridge.ts +10 -2
- package/store/agent/AgentPrompts.ts +4 -57
- package/store/agent/ScreenReader.ts +31 -6
- package/store/agent/StoreSurfaceSource.ts +13 -4
- package/store/agentic/StToolBuilder.ts +49 -22
- package/store/agentic/formFields.ts +21 -0
- package/store/agentic/index.ts +1 -0
- package/store/agentic/useFileFieldTool.ts +221 -0
- package/store/agentic/useRelationFieldTool.ts +3 -13
- package/types/client/csrTypes.d.ts +3 -0
- package/types/client/index.d.ts +1 -0
- package/types/client/route/LayoutDefinition.d.ts +16 -0
- package/types/client/route/PageDefinition.d.ts +15 -0
- package/types/client/route/RootLayoutDefinition.d.ts +17 -0
- package/types/client/route/RouteDefinition.d.ts +46 -0
- package/types/client/route/index.d.ts +7 -0
- package/types/client/route/resolveRouteModule.d.ts +24 -0
- package/types/client/route/routeArgs.d.ts +40 -0
- package/types/client/route/routeBuilders.d.ts +9 -0
- package/types/client/route.d.ts +1 -0
- package/types/common/TrustedProxy.d.ts +12 -0
- package/types/common/index.d.ts +1 -1
- package/types/common/mcpExposure.d.ts +2 -7
- package/types/constant/cascadePaths.d.ts +4 -2
- package/types/constant/deserialize.d.ts +6 -3
- package/types/constant/fieldInfo.d.ts +15 -8
- package/types/constant/types.d.ts +5 -2
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/document/documentSchema.d.ts +2 -0
- package/types/document/filterMeta.d.ts +8 -0
- package/types/document/into.d.ts +0 -8
- package/types/fetch/client/fetchClient.d.ts +2 -1
- package/types/fetch/client/httpClient.d.ts +1 -19
- package/types/fetch/client/index.d.ts +1 -0
- package/types/fetch/client/remoteError.d.ts +31 -0
- package/types/fetch/client/wsClient.d.ts +1 -1
- package/types/fetch/requestStorage.d.ts +15 -0
- package/types/server/akanApp.d.ts +11 -0
- package/types/server/akanOption.d.ts +4 -3
- package/types/server/akanServer.d.ts +33 -1
- package/types/server/di/diLifecycle.d.ts +12 -1
- package/types/server/di/utils.d.ts +1 -1
- package/types/server/index.d.ts +1 -0
- package/types/server/mcp/McpAuth.d.ts +52 -13
- package/types/server/mcp/McpDispatcher.d.ts +1 -13
- package/types/server/mcp/McpRateLimiter.d.ts +38 -0
- package/types/server/mcp/McpRouter.d.ts +17 -0
- package/types/server/mcp/PagePromptComposer.d.ts +34 -0
- package/types/server/mcp/index.d.ts +1 -0
- package/types/server/oauth/OAuthAuthorize.d.ts +26 -0
- package/types/server/oauth/OAuthClientIdMetadata.d.ts +53 -0
- package/types/server/oauth/OAuthErrors.d.ts +40 -0
- package/types/server/oauth/OAuthMetadata.d.ts +37 -0
- package/types/server/oauth/OAuthPkce.d.ts +9 -0
- package/types/server/oauth/OAuthRedirect.d.ts +20 -0
- package/types/server/oauth/OAuthRegistration.d.ts +21 -0
- package/types/server/oauth/OAuthRevocation.d.ts +31 -0
- package/types/server/oauth/OAuthToken.d.ts +35 -0
- package/types/server/oauth/index.d.ts +10 -0
- package/types/server/oauth/oauthTypes.d.ts +49 -0
- package/types/server/oauth.d.ts +1 -0
- package/types/server/routeTreeBuilder.d.ts +5 -2
- package/types/server/rscPagePrompts.d.ts +26 -0
- package/types/server/rscWorkerHost.d.ts +3 -0
- package/types/server/webRouter.d.ts +3 -0
- package/types/service/agent.service.d.ts +19 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +2 -0
- package/types/signal/endpointInfo.d.ts +3 -21
- package/types/signal/exception.d.ts +11 -0
- package/types/signal/guard.d.ts +9 -0
- package/types/signal/mcp/McpDocument.d.ts +19 -11
- package/types/signal/mcp/McpUriTemplate.d.ts +6 -0
- package/types/signal/mcp/index.d.ts +2 -1
- package/types/signal/mcp/mcpProtocol.d.ts +2 -0
- package/types/signal/mcp/pagePrompt.d.ts +50 -0
- package/types/signal/schema/JsonSchemaBuilder.d.ts +24 -3
- package/types/signal/signalContext.d.ts +6 -0
- package/types/signal/types.d.ts +8 -1
- package/types/store/action.d.ts +2 -2
- package/types/store/agent/AgentPrompts.d.ts +4 -17
- package/types/store/agent/ScreenReader.d.ts +10 -2
- package/types/store/agentic/StToolBuilder.d.ts +18 -7
- package/types/store/agentic/formFields.d.ts +12 -0
- package/types/store/agentic/index.d.ts +1 -0
- package/types/store/agentic/useFileFieldTool.d.ts +41 -0
- package/types/store/agentic/useRelationFieldTool.d.ts +1 -1
- package/types/ui/Agent/Chat.d.ts +8 -2
- package/types/ui/Agent/ChatCommands.d.ts +2 -4
- package/types/ui/Agent/attachment.d.ts +25 -4
- package/types/ui/Agent/useChatAttachments.d.ts +3 -2
- package/types/ui/Agent/useChatQueue.d.ts +3 -1
- package/types/ui/Agent/useSlashMenu.d.ts +2 -8
- package/types/ui/BottomSheet.d.ts +15 -2
- package/types/ui/CsrImage.d.ts +2 -2
- package/types/ui/Data/ListContainer.d.ts +1 -1
- package/types/ui/Dialog/Modal.d.ts +3 -1
- package/types/ui/Dialog/index.d.ts +1 -1
- package/types/ui/Dropdown.d.ts +5 -3
- package/types/ui/Empty.d.ts +3 -1
- package/types/ui/Field/Label.d.ts +4 -3
- package/types/ui/Field/index.d.ts +2 -2
- package/types/ui/Image.d.ts +3 -3
- package/types/ui/InfiniteScroll.d.ts +4 -1
- package/types/ui/Layout/BottomTab.d.ts +4 -2
- package/types/ui/Layout/LeftSider.d.ts +3 -1
- package/types/ui/Layout/RightSider.d.ts +4 -2
- package/types/ui/Layout/Sider.d.ts +7 -1
- package/types/ui/Layout/index.d.ts +4 -4
- package/types/ui/Load/Units.d.ts +3 -1
- package/types/ui/Load/View.d.ts +3 -1
- package/types/ui/Loading/Area.d.ts +9 -1
- package/types/ui/Menu.d.ts +3 -1
- package/types/ui/Modal.d.ts +7 -3
- package/types/ui/Model/Edit.d.ts +2 -4
- package/types/ui/Model/EditModal.d.ts +1 -1
- package/types/ui/Model/New.d.ts +2 -4
- package/types/ui/Model/Remove.d.ts +7 -1
- package/types/ui/Model/SureToRemove.d.ts +10 -1
- package/types/ui/Model/ViewEditModal.d.ts +7 -1
- package/types/ui/Pagination.d.ts +10 -2
- package/types/ui/Popconfirm.d.ts +8 -4
- package/types/ui/Select.d.ts +5 -3
- package/types/ui/Tab/Menu.d.ts +1 -1
- package/types/ui/Table.d.ts +9 -1
- package/types/ui/Toast.d.ts +43 -0
- package/types/ui/ToggleSelect.d.ts +11 -1
- package/types/ui/UiOverride/context.d.ts +3 -0
- package/types/ui/Unauthorized.d.ts +3 -1
- package/types/ui/index.d.ts +3 -1
- package/types/ui/triggerSlot.d.ts +24 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +1 -1
- package/types/vendor/use-agentic/ToolOutput.d.ts +11 -0
- package/types/vendor/use-agentic/ToolRunner.d.ts +70 -0
- package/types/vendor/use-agentic/index.d.ts +1 -0
- package/types/vendor/use-agentic/types.d.ts +7 -0
- package/types/webkit/bootCsr.d.ts +2 -2
- package/ui/Agent/Chat.tsx +14 -47
- package/ui/Agent/ChatCommands.ts +2 -4
- package/ui/Agent/attachment.ts +36 -8
- package/ui/Agent/useChatAttachments.ts +17 -8
- package/ui/Agent/useChatQueue.ts +6 -4
- package/ui/Agent/useSlashMenu.ts +9 -25
- package/ui/BottomSheet.tsx +72 -26
- package/ui/CsrImage.tsx +2 -2
- package/ui/Data/ListContainer.tsx +1 -1
- package/ui/Dialog/Modal.tsx +18 -9
- package/ui/Dropdown.tsx +21 -12
- package/ui/Empty.tsx +4 -2
- package/ui/Field/Label.tsx +5 -4
- package/ui/Field/Relation.tsx +4 -12
- package/ui/Field/index.tsx +2 -2
- package/ui/Image.tsx +3 -3
- package/ui/InfiniteScroll.tsx +6 -4
- package/ui/Layout/BottomTab.tsx +20 -12
- package/ui/Layout/LeftSider.tsx +17 -9
- package/ui/Layout/RightSider.tsx +18 -14
- package/ui/Layout/Sider.tsx +30 -23
- package/ui/Load/Units.tsx +14 -3
- package/ui/Load/View.tsx +8 -3
- package/ui/Loading/Area.tsx +19 -5
- package/ui/Menu.tsx +21 -14
- package/ui/Modal.tsx +17 -3
- package/ui/Model/Edit.tsx +12 -26
- package/ui/Model/EditModal.tsx +1 -1
- package/ui/Model/New.tsx +12 -21
- package/ui/Model/Remove.tsx +39 -16
- package/ui/Model/SureToRemove.tsx +45 -30
- package/ui/Model/ViewEditModal.tsx +34 -21
- package/ui/Pagination.tsx +18 -5
- package/ui/Popconfirm.tsx +30 -20
- package/ui/Select.tsx +218 -132
- package/ui/System/Messages.tsx +76 -122
- package/ui/Tab/Menu.tsx +1 -1
- package/ui/Table.tsx +16 -2
- package/ui/Toast.tsx +132 -0
- package/ui/ToggleSelect.tsx +15 -6
- package/ui/UiOverride/context.ts +4 -0
- package/ui/Unauthorized.tsx +10 -2
- package/ui/index.ts +18 -1
- package/ui/triggerSlot.tsx +52 -0
- package/vendor/use-agentic/AgentSession.ts +22 -92
- package/vendor/use-agentic/ToolOutput.ts +24 -0
- package/vendor/use-agentic/ToolRunner.ts +174 -0
- package/vendor/use-agentic/index.ts +1 -0
- package/vendor/use-agentic/types.ts +7 -0
- package/webkit/bootCsr.tsx +8 -2
package/constant/cascadePaths.ts
CHANGED
|
@@ -6,8 +6,10 @@ import type { ConstantModelRef } from "./via";
|
|
|
6
6
|
* Which end of a relation goes away with the other. `removeRef` removes what the field points at when this
|
|
7
7
|
* document is removed; `removeWith` removes this document when what the field points at is removed. The two
|
|
8
8
|
* read identically on a relation field, so the value has to name the direction — a mistake here is a data loss.
|
|
9
|
+
* `removeWithAny` is `removeWith` over an owner that is unknowable at build time, and it names the whole
|
|
10
|
+
* decision in one value so the widening cannot be declared apart from the direction it widens.
|
|
9
11
|
*/
|
|
10
|
-
export const cascadeActions = ["removeRef", "removeWith"] as const;
|
|
12
|
+
export const cascadeActions = ["removeRef", "removeWith", "removeWithAny"] as const;
|
|
11
13
|
export type CascadeAction = (typeof cascadeActions)[number];
|
|
12
14
|
|
|
13
15
|
/** How a `removeWith` field names the owner whose removal takes this document with it. */
|
|
@@ -21,7 +23,7 @@ export interface CascadeWithPath {
|
|
|
21
23
|
readonly typeKey: string | null;
|
|
22
24
|
/** The refNames `typeKey` may hold. Empty unless the field is polymorphic over an enum. */
|
|
23
25
|
readonly typeValues: readonly string[];
|
|
24
|
-
/** Set by `
|
|
26
|
+
/** Set by `cascade: "removeWithAny"`: `typeKey` is free-form, so the owner is whatever refName the row holds. */
|
|
25
27
|
readonly anyOwner: boolean;
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -35,7 +37,6 @@ export class CascadePaths {
|
|
|
35
37
|
|
|
36
38
|
collect(fieldMap: FieldObject) {
|
|
37
39
|
for (const [key, field] of Object.entries(fieldMap)) {
|
|
38
|
-
this.#assertPolymorphicIsWiredUp(key, field);
|
|
39
40
|
if (!field.cascade) continue;
|
|
40
41
|
this.#assertKnownAction(key, field.cascade);
|
|
41
42
|
if (field.cascade === "removeRef") this.removeRef.set(key, this.#readOwnedRelation(key, field));
|
|
@@ -44,16 +45,6 @@ export class CascadePaths {
|
|
|
44
45
|
return this;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/** `polymorphic` widens one thing and nothing else, so anywhere else it is a declaration that does nothing. */
|
|
48
|
-
#assertPolymorphicIsWiredUp(key: string, field: ConstantField) {
|
|
49
|
-
if (!field.polymorphic) return;
|
|
50
|
-
if (field.cascade === "removeWith" && field.refPath) return;
|
|
51
|
-
throw new Error(
|
|
52
|
-
`Field "${key}" declares polymorphic: "${field.polymorphic}", which only widens a ` +
|
|
53
|
-
`cascade: "removeWith" field that names its owner type with refPath`,
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
48
|
#assertKnownAction(key: string, action: CascadeAction) {
|
|
58
49
|
|
|
59
50
|
if (!cascadeActions.includes(action)) {
|
|
@@ -74,7 +65,15 @@ export class CascadePaths {
|
|
|
74
65
|
|
|
75
66
|
if (field.arrDepth > 0) throw new Error(`Cascade field "${key}" is an array and names more than one owner`);
|
|
76
67
|
if (field.isMap) throw new Error(`Cascade field "${key}" is a Map and names no owner`);
|
|
77
|
-
|
|
68
|
+
const anyOwner = field.cascade === "removeWithAny";
|
|
69
|
+
if (field.refPath) return this.#readPolymorphicOwner(key, field, fieldMap, anyOwner);
|
|
70
|
+
|
|
71
|
+
if (anyOwner) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Cascade field "${key}" declares cascade: "removeWithAny" and must name the field holding the owner's ` +
|
|
74
|
+
`refName with refPath: "<typeField>"`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
78
77
|
if (field.ref) {
|
|
79
78
|
this.#assertHoldsId(key, field);
|
|
80
79
|
return { key, modelRef: null, refName: field.ref, typeKey: null, typeValues: [], anyOwner: false };
|
|
@@ -88,18 +87,18 @@ export class CascadePaths {
|
|
|
88
87
|
);
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
#readPolymorphicOwner(key: string, field: ConstantField, fieldMap: FieldObject): CascadeWithPath {
|
|
90
|
+
#readPolymorphicOwner(key: string, field: ConstantField, fieldMap: FieldObject, anyOwner: boolean): CascadeWithPath {
|
|
92
91
|
if (field.ref) throw new Error(`Cascade field "${key}" declares both ref and refPath; keep one`);
|
|
93
92
|
this.#assertHoldsId(key, field);
|
|
94
93
|
const typeKey = field.refPath as string;
|
|
95
94
|
const typeField = fieldMap[typeKey];
|
|
96
95
|
if (!typeField) throw new Error(`Cascade field "${key}" declares refPath: "${typeKey}", which is not a field`);
|
|
97
|
-
if (
|
|
96
|
+
if (anyOwner) return this.#readAnyOwner(key, typeKey, typeField);
|
|
98
97
|
|
|
99
98
|
if (!typeField.enum) {
|
|
100
99
|
throw new Error(
|
|
101
100
|
`Cascade field "${key}" declares refPath: "${typeKey}", which must be an enumOf(...) naming the owner ` +
|
|
102
|
-
`refNames it may hold; declare
|
|
101
|
+
`refNames it may hold; declare cascade: "removeWithAny" to pay for a sweep on every removal instead`,
|
|
103
102
|
);
|
|
104
103
|
}
|
|
105
104
|
const typeValues = typeField.enum.values.map((value) => String(value));
|
|
@@ -110,14 +109,15 @@ export class CascadePaths {
|
|
|
110
109
|
|
|
111
110
|
if (typeField.enum) {
|
|
112
111
|
throw new Error(
|
|
113
|
-
`Cascade field "${key}" declares
|
|
112
|
+
`Cascade field "${key}" declares cascade: "removeWithAny" and a refPath naming an enumOf(...), which ` +
|
|
113
|
+
`already names its owners; use cascade: "removeWith"`,
|
|
114
114
|
);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (this.#primitiveNameOf(typeField) !== "String") {
|
|
118
118
|
throw new Error(
|
|
119
|
-
`Cascade field "${key}" declares
|
|
120
|
-
`holding the owner's refName`,
|
|
119
|
+
`Cascade field "${key}" declares cascade: "removeWithAny", so refPath: "${typeKey}" must be a String ` +
|
|
120
|
+
`field holding the owner's refName`,
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
123
|
return { key, modelRef: null, refName: null, typeKey, typeValues: [], anyOwner: true };
|
package/constant/deserialize.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Any,
|
|
3
|
+
applyFnToArrayObjects,
|
|
4
|
+
type Cls,
|
|
5
|
+
type EnumInstance,
|
|
6
|
+
FIELD_META,
|
|
7
|
+
PrimitiveRegistry,
|
|
8
|
+
type PrimitiveScalar,
|
|
9
|
+
} from "akanjs/base";
|
|
2
10
|
|
|
3
11
|
import { type ConstantCls, type ConstantModelRef, ConstantRegistry, type FieldProps, withSharedInstances } from ".";
|
|
4
12
|
|
|
@@ -50,6 +58,7 @@ const deserializeInput = <Input = unknown>(
|
|
|
50
58
|
: deserialize(field.modelRef, field.arrDepth, (value as Record<string, unknown>)[key], {
|
|
51
59
|
key,
|
|
52
60
|
nullable: field.nullable,
|
|
61
|
+
enum: field.enum,
|
|
53
62
|
}),
|
|
54
63
|
]),
|
|
55
64
|
) as unknown as Input;
|
|
@@ -57,15 +66,33 @@ const deserializeInput = <Input = unknown>(
|
|
|
57
66
|
}
|
|
58
67
|
};
|
|
59
68
|
|
|
69
|
+
interface DeserializeOption {
|
|
70
|
+
key?: string;
|
|
71
|
+
nullable?: boolean;
|
|
72
|
+
convertFn?: (value: unknown) => unknown;
|
|
73
|
+
enum?: EnumInstance;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const assertEnumValue = (enumRef: EnumInstance, value: unknown, key?: string): void => {
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
for (const item of value) assertEnumValue(enumRef, item, key);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (value === null || value === undefined || enumRef.has(value as never)) return;
|
|
82
|
+
throw new Error(`Invalid Enum Value in ${key}: ${String(value)} is not one of ${enumRef.values.join(", ")}`);
|
|
83
|
+
};
|
|
84
|
+
|
|
60
85
|
export const deserialize = (
|
|
61
86
|
argRef: ConstantModelRef | PrimitiveScalar,
|
|
62
87
|
arrDepth: number,
|
|
63
88
|
value: unknown,
|
|
64
|
-
{ key, nullable = false, convertFn
|
|
89
|
+
{ key, nullable = false, convertFn, enum: enumRef }: DeserializeOption,
|
|
65
90
|
) => {
|
|
66
91
|
if (nullable && (value === null || value === undefined)) return null;
|
|
67
92
|
else if (!nullable && (value === null || value === undefined) && argRef !== Any)
|
|
68
93
|
throw new Error(`Invalid Value (Nullable) in ${key} ${argRef} for value ${value}`);
|
|
69
94
|
|
|
70
|
-
|
|
95
|
+
const result = withSharedInstances(() => deserializeInput(value, argRef, arrDepth, convertFn)) as object[];
|
|
96
|
+
if (enumRef) assertEnumValue(enumRef, result, key);
|
|
97
|
+
return result;
|
|
71
98
|
};
|
package/constant/fieldInfo.ts
CHANGED
|
@@ -106,11 +106,6 @@ export interface ConstantFieldProps<
|
|
|
106
106
|
validate?: (value: FieldValue, model: any) => boolean;
|
|
107
107
|
text?: TextFieldRole;
|
|
108
108
|
cascade?: CascadeAction;
|
|
109
|
-
/**
|
|
110
|
-
* Widens a `cascade: "removeWith"` field whose `refPath` names a free-form `String` instead of an `enumOf`:
|
|
111
|
-
* the owner is whatever refName the row happens to hold, found by sweeping at removal time.
|
|
112
|
-
*/
|
|
113
|
-
polymorphic?: "any";
|
|
114
109
|
/**
|
|
115
110
|
* Renders on the page, never reaches an agent. Stripped wherever a value is masked for an AI caller — the
|
|
116
111
|
* in-page agent's reads and every MCP result — and left untouched everywhere else, so a `File`'s blur
|
|
@@ -209,7 +204,6 @@ interface ConstantFieldBuildProps<
|
|
|
209
204
|
validate?: (value: FieldValue, model: any) => boolean;
|
|
210
205
|
text?: TextFieldRole;
|
|
211
206
|
cascade?: CascadeAction;
|
|
212
|
-
polymorphic?: "any";
|
|
213
207
|
visual: boolean;
|
|
214
208
|
modelRef: ConstantModelRef;
|
|
215
209
|
arrDepth: number;
|
|
@@ -335,7 +329,6 @@ export class ConstantField<
|
|
|
335
329
|
readonly validate?: (value: FieldValue, model: any) => boolean;
|
|
336
330
|
readonly text?: TextFieldRole;
|
|
337
331
|
readonly cascade?: CascadeAction;
|
|
338
|
-
readonly polymorphic?: "any";
|
|
339
332
|
readonly visual: boolean;
|
|
340
333
|
readonly modelRef: ConstantModelRef;
|
|
341
334
|
readonly arrDepth: number;
|
|
@@ -369,7 +362,6 @@ export class ConstantField<
|
|
|
369
362
|
this.validate = props.validate;
|
|
370
363
|
this.text = props.text;
|
|
371
364
|
this.cascade = props.cascade;
|
|
372
|
-
this.polymorphic = props.polymorphic;
|
|
373
365
|
this.visual = props.visual;
|
|
374
366
|
this.modelRef = props.modelRef;
|
|
375
367
|
this.arrDepth = props.arrDepth;
|
|
@@ -450,7 +442,6 @@ export class ConstantField<
|
|
|
450
442
|
validate: option.validate,
|
|
451
443
|
text: option.text,
|
|
452
444
|
cascade: option.cascade,
|
|
453
|
-
polymorphic: option.polymorphic,
|
|
454
445
|
visual: option.visual ?? false,
|
|
455
446
|
modelRef,
|
|
456
447
|
arrDepth: arrDepth,
|
|
@@ -498,7 +489,6 @@ export class ConstantField<
|
|
|
498
489
|
validate: this.validate,
|
|
499
490
|
text: this.text,
|
|
500
491
|
cascade: this.cascade,
|
|
501
|
-
polymorphic: this.polymorphic,
|
|
502
492
|
visual: this.visual,
|
|
503
493
|
modelRef: this.modelRef,
|
|
504
494
|
arrDepth: this.arrDepth,
|
|
@@ -523,19 +513,35 @@ export interface FieldObject {
|
|
|
523
513
|
*/
|
|
524
514
|
type WithoutTextRole<Option> = Option extends unknown ? Omit<Option, "text"> : never;
|
|
525
515
|
|
|
516
|
+
/**
|
|
517
|
+
* A wildcard owner is read off the row at removal time, so `removeWithAny` without a `refPath` names nothing to
|
|
518
|
+
* read it from — and the price of that action (no cascade in the app removes in one query) is one nobody should
|
|
519
|
+
* pay for a declaration that cascades nothing. Pairing the two in the option type refuses it at the call site;
|
|
520
|
+
* `CascadePaths` keeps the same refusal for the macro and bundled paths that reach it without a typecheck.
|
|
521
|
+
*/
|
|
522
|
+
type CascadeOption =
|
|
523
|
+
| { cascade?: "removeRef" | "removeWith"; refPath?: string }
|
|
524
|
+
| { cascade: "removeWithAny"; refPath: string };
|
|
525
|
+
|
|
526
|
+
type WithCascadePair<Option> = Omit<Option, "cascade" | "refPath"> & CascadeOption;
|
|
527
|
+
|
|
526
528
|
type FieldOption<
|
|
527
529
|
Value extends ConstantFieldTypeInput,
|
|
528
530
|
MapValue = Value extends MapConstructor ? typeof PrimitiveScalar : never,
|
|
529
531
|
Metadata extends { [key: string]: any } = { [key: string]: any },
|
|
530
532
|
_FieldToValue = FieldToValue<Value, MapValue> | null | undefined,
|
|
531
533
|
> =
|
|
532
|
-
|
|
|
533
|
-
|
|
534
|
-
|
|
534
|
+
| WithCascadePair<
|
|
535
|
+
Omit<
|
|
536
|
+
ConstantFieldProps<ConstantFieldKind, _FieldToValue, MapValue, Metadata>,
|
|
537
|
+
"enum" | "meta" | "nullable" | "fieldType" | "select"
|
|
538
|
+
>
|
|
535
539
|
>
|
|
536
|
-
|
|
|
537
|
-
|
|
538
|
-
|
|
540
|
+
| WithCascadePair<
|
|
541
|
+
Omit<
|
|
542
|
+
ConstantFieldProps<ConstantFieldKind, SingleValue<_FieldToValue>, MapValue, Metadata>,
|
|
543
|
+
"enum" | "meta" | "nullable" | "fieldType" | "select"
|
|
544
|
+
>
|
|
539
545
|
>[];
|
|
540
546
|
|
|
541
547
|
export type PlainTypeToFieldType<PlainType> = PlainType extends [infer First, ...infer Rest]
|
package/constant/purify.ts
CHANGED
|
@@ -105,6 +105,8 @@ const purify = (field: FieldProps, key: string, value: unknown, self: Record<str
|
|
|
105
105
|
throw new Error(`Invalid Date Value (Default) in ${key} for value ${value}`);
|
|
106
106
|
if ([String, ID].includes(field.modelRef as unknown as StringConstructor | typeof ID) && (value === "" || !value))
|
|
107
107
|
throw new Error(`Invalid String Value (Default) in ${key} for value ${value}`);
|
|
108
|
+
if (field.enum && !field.enum.has(value as never))
|
|
109
|
+
throw new Error(`Invalid Enum Value in ${key}: ${String(value)} is not one of ${field.enum.values.join(", ")}`);
|
|
108
110
|
if (field.validate && !field.validate(value, self))
|
|
109
111
|
throw new Error(`Invalid Value (Failed to pass validation) / ${value} in ${key}`);
|
|
110
112
|
if (!field.nullable && !value && value !== 0 && value !== false && (field.modelRef as Cls) !== Any)
|
package/constant/types.ts
CHANGED
|
@@ -63,18 +63,22 @@ export class BaseInsight {
|
|
|
63
63
|
declare count: number;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
export interface
|
|
66
|
+
export interface ProtoLightFile {
|
|
67
67
|
id: string;
|
|
68
68
|
filename: string;
|
|
69
69
|
abstractData: string | null;
|
|
70
70
|
imageSize: [number, number];
|
|
71
|
-
progress
|
|
71
|
+
progress?: number | null;
|
|
72
72
|
url: string;
|
|
73
73
|
size: number;
|
|
74
74
|
status: string;
|
|
75
75
|
createdAt: Dayjs;
|
|
76
76
|
updatedAt: Dayjs;
|
|
77
77
|
removedAt: Dayjs | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ProtoFile extends ProtoLightFile {
|
|
81
|
+
progress: number | null;
|
|
78
82
|
mimetype: string;
|
|
79
83
|
encoding: string;
|
|
80
84
|
origin: string | null;
|
|
@@ -146,6 +146,7 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
|
|
|
146
146
|
password: ["Password", "비밀번호"],
|
|
147
147
|
passwordConfirm: ["Password Confirm", "비밀번호 확인"],
|
|
148
148
|
noOptions: ["Can't find any datas", "데이터를 찾을 수 없습니다."],
|
|
149
|
+
noSelection: ["No selection", "선택 안 함"],
|
|
149
150
|
addModel: ["Add {model}", "{model} 추가"],
|
|
150
151
|
createModel: ["Create {model}", "{model} 생성하기"],
|
|
151
152
|
createSuccess: ["{model} create success", "{model} 생성 완료"],
|
|
@@ -53,6 +53,26 @@ export class DocumentSchema<Doc = unknown> {
|
|
|
53
53
|
return this;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
removePre<HookDoc = Doc>(type: SaveEventType, hook: DocumentSaveHook<HookDoc>) {
|
|
57
|
+
const hooks = this.preHooks.get(type);
|
|
58
|
+
if (hooks)
|
|
59
|
+
this.preHooks.set(
|
|
60
|
+
type,
|
|
61
|
+
hooks.filter((registered) => registered !== (hook as unknown)),
|
|
62
|
+
);
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
removePost<HookDoc = Doc>(type: SaveEventType, hook: DocumentSaveHook<HookDoc>) {
|
|
67
|
+
const hooks = this.postHooks.get(type);
|
|
68
|
+
if (hooks)
|
|
69
|
+
this.postHooks.set(
|
|
70
|
+
type,
|
|
71
|
+
hooks.filter((registered) => registered !== (hook as unknown)),
|
|
72
|
+
);
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
56
76
|
hook(type: DocumentHookName, hook: DocumentSaveHook<Doc>) {
|
|
57
77
|
const [, phase, event] = /^(before|after)(.+)$/.exec(type) ?? [];
|
|
58
78
|
if (!phase || !event) throw new Error(`Invalid document hook: ${type}`);
|
package/document/filterMeta.ts
CHANGED
|
@@ -100,6 +100,27 @@ export const fillMissingFilterArgs = (filterInfo: FilterInfo, args: unknown[]) =
|
|
|
100
100
|
return [...args, ...Array(filterInfo.args.length - args.length).fill(undefined)];
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
const queryOptionKeys = new Set(["select", "skip", "limit", "sort", "sample"]);
|
|
104
|
+
|
|
105
|
+
const isQueryOptionArg = (value: unknown) => {
|
|
106
|
+
if (!value || typeof value !== "object") return false;
|
|
107
|
+
const proto = Object.getPrototypeOf(value);
|
|
108
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
109
|
+
return Object.keys(value).every((key) => queryOptionKeys.has(key));
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Splits a generated filter method's arguments into the filter's own args and the trailing query option.
|
|
114
|
+
* Shared by the database and service resolvers so the two cannot drift into disagreeing about what an option is.
|
|
115
|
+
*/
|
|
116
|
+
export const splitFilterArgs = (filterInfo: FilterInfo, args: unknown[]) => {
|
|
117
|
+
const hasQueryOption = args.length > filterInfo.args.length || isQueryOptionArg(args.at(-1));
|
|
118
|
+
return {
|
|
119
|
+
queryArgs: fillMissingFilterArgs(filterInfo, hasQueryOption ? args.slice(0, -1) : args),
|
|
120
|
+
queryOption: (hasQueryOption ? args.at(-1) : {}) as Record<string, unknown>,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
103
124
|
export interface FilterArgInfo {
|
|
104
125
|
name: string;
|
|
105
126
|
argRef: ConstantFieldType;
|
package/document/into.ts
CHANGED
|
@@ -24,14 +24,6 @@ interface DefaultMdlStats<
|
|
|
24
24
|
pickById: (docId: string | undefined, projection?: _Projection) => Promise<TDocument>;
|
|
25
25
|
sample: (query: _FilterQuery, size?: number) => Promise<TDocument[]>;
|
|
26
26
|
sampleOne: (query: _FilterQuery) => Promise<TDocument | null>;
|
|
27
|
-
preSaveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
|
|
28
|
-
postSaveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
|
|
29
|
-
preCreateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
|
|
30
|
-
postCreateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
|
|
31
|
-
preUpdateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
|
|
32
|
-
postUpdateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
|
|
33
|
-
preRemoveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
|
|
34
|
-
postRemoveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
|
|
35
27
|
listenPre: (
|
|
36
28
|
eventType: SaveEventType,
|
|
37
29
|
listener: (doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>,
|
|
@@ -19,10 +19,16 @@ import type {
|
|
|
19
19
|
} from "akanjs/signal";
|
|
20
20
|
import { agentTurnConstant } from "../agentTurn";
|
|
21
21
|
import type { ClientSignal, FetchClientType, FetchSignalInput, MergeAllFetchTypes, SliceMeta } from "../fetchType";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
claimRequestQuery,
|
|
24
|
+
recordRequestQuery,
|
|
25
|
+
cookies as requestCookies,
|
|
26
|
+
headers as requestHeaders,
|
|
27
|
+
} from "../requestStorage";
|
|
23
28
|
import type { GetSliceMetaObjFromDatabaseSignals } from "../types";
|
|
24
29
|
import { FetchHandle } from "./fetchHandle";
|
|
25
|
-
import {
|
|
30
|
+
import { HttpClient } from "./httpClient";
|
|
31
|
+
import type { ErrorConstructor } from "./remoteError";
|
|
26
32
|
import { SliceInitHandle } from "./sliceInitHandle";
|
|
27
33
|
import { WsClient } from "./wsClient";
|
|
28
34
|
|
|
@@ -302,8 +308,6 @@ export class FetchClient {
|
|
|
302
308
|
const parseReturn = this.#makeReturnParser(endpoint.returns);
|
|
303
309
|
const { bodyArgs, uploadArgs } = FetchClient.classifyHttpArgs(endpoint.args);
|
|
304
310
|
switch (endpoint.type) {
|
|
305
|
-
|
|
306
|
-
case "prompt":
|
|
307
311
|
case "query": {
|
|
308
312
|
const queryFn = async (...argData: unknown[]) => {
|
|
309
313
|
const args = argData.slice(0, argLength);
|
|
@@ -318,6 +322,7 @@ export class FetchClient {
|
|
|
318
322
|
const claim = baseUrl
|
|
319
323
|
? { value: requestQuery(), owned: true }
|
|
320
324
|
: claimRequestQuery(FetchClient.#makeRequestQueryCacheKey(this.origin, url, headers), requestQuery);
|
|
325
|
+
recordRequestQuery({ key, args: Object.fromEntries(argMap), returns: endpoint.returns, value: claim.value });
|
|
321
326
|
const response = await claim.value;
|
|
322
327
|
const payload = claim.owned ? response : FetchClient.#deepCopy(response);
|
|
323
328
|
return parseReturn(payload, { crystalize: option?.crystalize ?? true });
|
|
@@ -346,7 +351,6 @@ export class FetchClient {
|
|
|
346
351
|
}
|
|
347
352
|
#registerEndpoint(key: string, endpoint: SerializedEndpoint, prefix?: string) {
|
|
348
353
|
switch (endpoint.type) {
|
|
349
|
-
case "prompt":
|
|
350
354
|
case "query": {
|
|
351
355
|
this.#setHandlerFactory(key, () => this.#makeHttpFn(key, endpoint, prefix));
|
|
352
356
|
return;
|
|
@@ -455,8 +459,10 @@ export class FetchClient {
|
|
|
455
459
|
const updateGuards = signal.updateGuards ?? signal.cruGuards;
|
|
456
460
|
const removeGuards = signal.removeGuards ?? signal.cruGuards;
|
|
457
461
|
|
|
458
|
-
const mcp = (verb: keyof NonNullable<SerializedSignal["mcp"]>) =>
|
|
459
|
-
signal.mcp?.[verb] === false ? { mcp: false as const } : {}
|
|
462
|
+
const mcp = (verb: keyof NonNullable<SerializedSignal["mcp"]>) => ({
|
|
463
|
+
...(signal.mcp?.[verb] === false ? { mcp: false as const } : {}),
|
|
464
|
+
...(signal.agents?.[verb] === false ? { agents: false as const } : {}),
|
|
465
|
+
});
|
|
460
466
|
const endpoint: { [key: string]: SerializedEndpoint } = {};
|
|
461
467
|
if (signal.getGuards) {
|
|
462
468
|
endpoint[names.model] = {
|
|
@@ -630,7 +636,10 @@ export class FetchClient {
|
|
|
630
636
|
live: `${refName}Live${capSuffix}`,
|
|
631
637
|
};
|
|
632
638
|
|
|
633
|
-
const mcp =
|
|
639
|
+
const mcp = {
|
|
640
|
+
...(slice.mcp === false ? { mcp: false as const } : {}),
|
|
641
|
+
...(slice.agents === false ? { agents: false as const } : {}),
|
|
642
|
+
};
|
|
634
643
|
const endpoint: { [key: string]: SerializedEndpoint } = {
|
|
635
644
|
[names.list]: {
|
|
636
645
|
type: "query",
|
|
@@ -1,26 +1,5 @@
|
|
|
1
1
|
import type { HttpMutationMethod, SerializedArg } from "akanjs/signal";
|
|
2
|
-
|
|
3
|
-
export interface ErrorResponsePayload {
|
|
4
|
-
error: string;
|
|
5
|
-
statusCode?: number;
|
|
6
|
-
data?: Record<string, unknown>;
|
|
7
|
-
details?: unknown;
|
|
8
|
-
path?: string;
|
|
9
|
-
timestamp?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface RestoredError extends Error {
|
|
13
|
-
error?: string;
|
|
14
|
-
statusCode?: number;
|
|
15
|
-
data?: unknown;
|
|
16
|
-
details?: unknown;
|
|
17
|
-
path?: string;
|
|
18
|
-
timestamp?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ErrorConstructor {
|
|
22
|
-
fromJSON: (payload: ErrorResponsePayload) => RestoredError;
|
|
23
|
-
}
|
|
2
|
+
import { type ErrorConstructor, type RestoredError, restoreRemoteError } from "./remoteError";
|
|
24
3
|
|
|
25
4
|
export interface HttpClientOptions {
|
|
26
5
|
headers?: Record<string, string>;
|
|
@@ -199,14 +178,7 @@ export class HttpClient {
|
|
|
199
178
|
}
|
|
200
179
|
|
|
201
180
|
#restoreError(body: unknown, fallbackStatusCode: number): RestoredError {
|
|
202
|
-
|
|
203
|
-
body && typeof body === "object" && "error" in body
|
|
204
|
-
? ({ statusCode: fallbackStatusCode, ...(body as Record<string, unknown>) } as ErrorResponsePayload)
|
|
205
|
-
: ({ error: String(body), statusCode: fallbackStatusCode } satisfies ErrorResponsePayload);
|
|
206
|
-
if (this.ErrorCls) return this.ErrorCls.fromJSON(payload);
|
|
207
|
-
const error = new Error(payload.error);
|
|
208
|
-
Object.assign(error, payload);
|
|
209
|
-
return error;
|
|
181
|
+
return restoreRemoteError(body, fallbackStatusCode, this.ErrorCls);
|
|
210
182
|
}
|
|
211
183
|
static makePath(key: string, paramArgs: SerializedArg[], prefix?: string) {
|
|
212
184
|
const paramPath = paramArgs.length > 0 ? `/${paramArgs.map((arg) => `:${arg.name}`).join("/")}` : "";
|
package/fetch/client/index.ts
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface ErrorResponsePayload {
|
|
2
|
+
error: string;
|
|
3
|
+
statusCode?: number;
|
|
4
|
+
data?: Record<string, unknown>;
|
|
5
|
+
details?: unknown;
|
|
6
|
+
path?: string;
|
|
7
|
+
timestamp?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RestoredError extends Error {
|
|
11
|
+
error?: string;
|
|
12
|
+
statusCode?: number;
|
|
13
|
+
data?: unknown;
|
|
14
|
+
details?: unknown;
|
|
15
|
+
path?: string;
|
|
16
|
+
timestamp?: string;
|
|
17
|
+
toJSON?: () => ErrorResponsePayload;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ErrorConstructor {
|
|
21
|
+
fromJSON: (payload: ErrorResponsePayload) => RestoredError;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Rebuilds what a remote process reported: the caller's own `Err` when this client was given one, and a
|
|
26
|
+
* duck-typed `Error` carrying the same payload otherwise.
|
|
27
|
+
*
|
|
28
|
+
* The `toJSON` is what makes the second case rethrowable. A server process that calls another with `{ origin }`
|
|
29
|
+
* and lets the failure propagate is answered by `SignalContext.try`, which forwards a payload only when the
|
|
30
|
+
* thrown value carries both `statusCode` and `toJSON`; anything else is this repo's own bug and is generalized
|
|
31
|
+
* to `Internal Server Error`. Without it the hop loses the dictionary key and its data, and the browser toasts
|
|
32
|
+
* the generalization instead of the sentence the remote endpoint chose.
|
|
33
|
+
*/
|
|
34
|
+
export const restoreRemoteError = (
|
|
35
|
+
body: unknown,
|
|
36
|
+
fallbackStatusCode: number,
|
|
37
|
+
ErrorCls?: ErrorConstructor,
|
|
38
|
+
): RestoredError => {
|
|
39
|
+
const payload =
|
|
40
|
+
body && typeof body === "object" && "error" in body
|
|
41
|
+
? ({ statusCode: fallbackStatusCode, ...(body as Record<string, unknown>) } as ErrorResponsePayload)
|
|
42
|
+
: ({ error: String(body), statusCode: fallbackStatusCode } satisfies ErrorResponsePayload);
|
|
43
|
+
if (ErrorCls) return ErrorCls.fromJSON(payload);
|
|
44
|
+
const error = new Error(payload.error) as RestoredError;
|
|
45
|
+
Object.assign(error, payload);
|
|
46
|
+
|
|
47
|
+
const json: ErrorResponsePayload = {
|
|
48
|
+
error: payload.error,
|
|
49
|
+
statusCode: payload.statusCode ?? fallbackStatusCode,
|
|
50
|
+
...(payload.details !== undefined ? { details: payload.details } : {}),
|
|
51
|
+
...(payload.data !== undefined ? { data: payload.data } : {}),
|
|
52
|
+
...(payload.path !== undefined ? { path: payload.path } : {}),
|
|
53
|
+
...(payload.timestamp !== undefined ? { timestamp: payload.timestamp } : {}),
|
|
54
|
+
};
|
|
55
|
+
error.toJSON = () => json;
|
|
56
|
+
return error;
|
|
57
|
+
};
|
package/fetch/client/wsClient.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
WebsocketResData,
|
|
8
8
|
WebsocketSubscribeAck,
|
|
9
9
|
} from "akanjs/signal";
|
|
10
|
-
import type
|
|
10
|
+
import { type ErrorConstructor, type RestoredError, restoreRemoteError } from "./remoteError";
|
|
11
11
|
|
|
12
12
|
export interface WsClientReconnectOptions {
|
|
13
13
|
enabled?: boolean;
|
|
@@ -216,14 +216,7 @@ export class WsClient {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
#restoreError(body: unknown): RestoredError {
|
|
219
|
-
|
|
220
|
-
body && typeof body === "object" && "error" in body
|
|
221
|
-
? (body as ErrorResponsePayload)
|
|
222
|
-
: ({ error: String(body), statusCode: 500 } satisfies ErrorResponsePayload);
|
|
223
|
-
if (this.ErrorCls) return this.ErrorCls.fromJSON(payload);
|
|
224
|
-
const error = new Error(payload.error);
|
|
225
|
-
Object.assign(error, payload);
|
|
226
|
-
return error;
|
|
219
|
+
return restoreRemoteError(body, 500, this.ErrorCls);
|
|
227
220
|
}
|
|
228
221
|
|
|
229
222
|
destroy() {
|
package/fetch/requestStorage.ts
CHANGED
|
@@ -12,11 +12,20 @@ export interface AkanDynamicUsage {
|
|
|
12
12
|
cookies: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** One `fetch.*` query a request made, by endpoint and argument — what a page's data footprint is read off. */
|
|
16
|
+
export interface RequestQueryRecord {
|
|
17
|
+
key: string;
|
|
18
|
+
args: Record<string, unknown>;
|
|
19
|
+
returns: { refName: string; modelType?: string; arrDepth?: number; nullable?: boolean };
|
|
20
|
+
value: Promise<unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
export interface AkanRequestStore {
|
|
16
24
|
request: Request;
|
|
17
25
|
theme?: AkanTheme;
|
|
18
26
|
frameState?: unknown;
|
|
19
27
|
queryCache: Map<string, Promise<unknown>>;
|
|
28
|
+
queryLog: RequestQueryRecord[];
|
|
20
29
|
policy: AkanRequestPolicy;
|
|
21
30
|
dynamicUsage: AkanDynamicUsage;
|
|
22
31
|
}
|
|
@@ -62,6 +71,7 @@ export function createRequestStore(
|
|
|
62
71
|
return {
|
|
63
72
|
request,
|
|
64
73
|
queryCache: new Map(),
|
|
74
|
+
queryLog: [],
|
|
65
75
|
policy: { ...createRequestPolicy(), ...policy },
|
|
66
76
|
dynamicUsage: { headers: false, cookies: false },
|
|
67
77
|
};
|
|
@@ -189,6 +199,11 @@ export function claimRequestQuery<T>(key: string, factory: () => Promise<T>): {
|
|
|
189
199
|
return { value: promise, owned: true };
|
|
190
200
|
}
|
|
191
201
|
|
|
202
|
+
/** Appends to the active request's query log; outside a request there is nothing to read it, so nothing is kept. */
|
|
203
|
+
export function recordRequestQuery(record: RequestQueryRecord): void {
|
|
204
|
+
getRequestStore()?.queryLog.push(record);
|
|
205
|
+
}
|
|
206
|
+
|
|
192
207
|
/** Deduplicates a promise-producing query within the active request. */
|
|
193
208
|
export function memoizeRequestQuery<T>(key: string, factory: () => Promise<T>): Promise<T> {
|
|
194
209
|
return claimRequestQuery(key, factory).value;
|
|
Binary file
|
|
Binary file
|