@qilin-agent/client-ui-settings 3.1.5 → 3.1.6
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.i18n.yaml +2 -2
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +131 -48
- package/lib/index.js +9 -7
- package/lib/types/client/schema.d.ts +5 -4
- package/lib/types/index.d.ts +16 -3
- package/package.json +11 -10
package/README.i18n.yaml
CHANGED
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
en: ffd6c51916031c1d
|
|
40
40
|
zh: 703566b0885fd19e
|
|
41
41
|
/qilin-agent-client-ui-settings/understand-the-implementation/schema-service:
|
|
42
|
-
en:
|
|
43
|
-
zh:
|
|
42
|
+
en: aa86a5f5e64362f3
|
|
43
|
+
zh: 37e9a4da01cf0cf0
|
|
44
44
|
/qilin-agent-client-ui-settings/further-exploration:
|
|
45
45
|
en: 2ce4e8d3a90e61ce
|
|
46
46
|
zh: 9b5510de7f4b320d
|
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ The plugin injects `remote` with its `settings` namespace, resolves Host persist
|
|
|
65
65
|
|
|
66
66
|
### Schema service
|
|
67
67
|
|
|
68
|
-
`ctx.settingsSchema` performs synchronous schema rehydration, validation, and immutable path editing for settings plugins. A section that is not a plain object, fails its rehydrated schema, or carries a schema envelope this client cannot rehydrate publishes no value at all.
|
|
68
|
+
`ctx.settingsSchema` performs synchronous schema rehydration, validation, and immutable path editing for settings plugins; it delegates to [`@qilin-agent/client-schema-form`](../schema-form/README.md), the shared model layer that shipped editors import directly. A section that is not a plain object, fails its rehydrated schema, or carries a schema envelope this client cannot rehydrate publishes no value at all.
|
|
69
69
|
|
|
70
70
|
</details>
|
|
71
71
|
|
package/README.zh.md
CHANGED
|
@@ -65,7 +65,7 @@ kind: "package-reference"
|
|
|
65
65
|
|
|
66
66
|
### Schema 服务
|
|
67
67
|
|
|
68
|
-
`ctx.settingsSchema` 为设置插件执行同步 schema
|
|
68
|
+
`ctx.settingsSchema` 为设置插件执行同步 schema 重建、校验与不可变路径编辑;它委派给 [`@qilin-agent/client-schema-form`](../schema-form/README.zh.md)——随包发布的编辑器直接导入的共享模型层。分区不是普通对象、未通过其重建后的 schema 校验、或携带本客户端无法重建的 schema 信封时,一律不发布任何值。
|
|
69
69
|
|
|
70
70
|
</details>
|
|
71
71
|
|
package/lib/client.js
CHANGED
|
@@ -897,19 +897,100 @@ window.__ModuleLoader__.load({
|
|
|
897
897
|
"preserve"
|
|
898
898
|
], ({ inner }, isInner) => inner.toString(isInner));
|
|
899
899
|
//#endregion
|
|
900
|
-
//#region
|
|
901
|
-
/**
|
|
900
|
+
//#region ../schema-form/src/model.ts
|
|
901
|
+
/**
|
|
902
|
+
* Schema introspection and draft-editing helpers behind settings editors.
|
|
903
|
+
* The serialized schemastery envelope (`schema.toJSON()`) rehydrates into a
|
|
904
|
+
* live validator whose node relations (`dict`/`inner`) editors probe for
|
|
905
|
+
* field presence and roles; drafts are edited immutably by path.
|
|
906
|
+
* @module @qilin-agent/client-schema-form/model
|
|
907
|
+
*/
|
|
908
|
+
/**
|
|
909
|
+
* Rehydrate a serialized schema envelope into a live validator/node tree.
|
|
910
|
+
* @param serialized - `schema.toJSON()` output received over the wire.
|
|
911
|
+
* @returns the root schema node.
|
|
912
|
+
*/
|
|
913
|
+
function rehydrateSchema(serialized) {
|
|
914
|
+
return new Schema(serialized);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Validate a draft against a rehydrated schema.
|
|
918
|
+
* @param schema - rehydrated root node.
|
|
919
|
+
* @param draft - candidate value.
|
|
920
|
+
* @returns the validation failure message, or `undefined` when the draft passes.
|
|
921
|
+
*/
|
|
922
|
+
function validateDraft(schema, draft) {
|
|
923
|
+
try {
|
|
924
|
+
schema(draft);
|
|
925
|
+
return;
|
|
926
|
+
} catch (error) {
|
|
927
|
+
return error instanceof Error ? error.message : String(error);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Resolve the schema node at a settings path (the configurable-provider
|
|
932
|
+
* directory's `settingsPath` vocabulary): object properties by name, dict
|
|
933
|
+
* entries through `inner`. An unresolvable segment returns `undefined` so
|
|
934
|
+
* the caller falls back instead of rendering a wrong subtree.
|
|
935
|
+
* @param root - rehydrated section root node.
|
|
936
|
+
* @param path - key path from the section root.
|
|
937
|
+
* @returns the node describing that position, or `undefined`.
|
|
938
|
+
*/
|
|
939
|
+
function nodeAtPath(root, path) {
|
|
940
|
+
let node = root;
|
|
941
|
+
for (const key of path) {
|
|
942
|
+
if (node === void 0) return void 0;
|
|
943
|
+
if (node.type === "object") node = node.dict?.[key];
|
|
944
|
+
else if (node.type === "dict" || node.type === "array") node = node.inner;
|
|
945
|
+
else return void 0;
|
|
946
|
+
}
|
|
947
|
+
return node;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Read a nested value by path.
|
|
951
|
+
* @param value - root value (draft or fallback layer).
|
|
952
|
+
* @param path - key path from the root; array indexes as strings.
|
|
953
|
+
* @returns the value at the path, or `undefined` along a missing branch.
|
|
954
|
+
*/
|
|
955
|
+
function getPath(value, path) {
|
|
956
|
+
let current = value;
|
|
957
|
+
for (const key of path) {
|
|
958
|
+
if (Array.isArray(current)) {
|
|
959
|
+
current = current[Number(key)];
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
if (typeof current !== "object" || current === null) return void 0;
|
|
963
|
+
current = current[key];
|
|
964
|
+
}
|
|
965
|
+
return current;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Whether a draft explicitly carries the path (its presence marks a user
|
|
969
|
+
* override, independent of the value stored there).
|
|
970
|
+
* @param value - root value (draft or fallback layer).
|
|
971
|
+
* @param path - key path from the root; array indexes as strings.
|
|
972
|
+
* @returns whether the path's final key exists on its parent.
|
|
973
|
+
*/
|
|
974
|
+
function hasPath(value, path) {
|
|
975
|
+
if (path.length === 0) return value !== void 0;
|
|
976
|
+
const parent = getPath(value, path.slice(0, -1));
|
|
977
|
+
const key = path[path.length - 1];
|
|
978
|
+
if (Array.isArray(parent)) return Number(key) < parent.length;
|
|
979
|
+
if (typeof parent !== "object" || parent === null) return false;
|
|
980
|
+
return key in parent;
|
|
981
|
+
}
|
|
902
982
|
function cloneContainer(container, key) {
|
|
903
983
|
if (Array.isArray(container)) return [...container];
|
|
904
984
|
if (typeof container === "object" && container !== null) return { ...container };
|
|
905
985
|
return /^\d+$/.test(key) ? [] : {};
|
|
906
986
|
}
|
|
987
|
+
/** Clone the container spine down to the leaf's parent, materializing missing intermediates. */
|
|
907
988
|
function cloneSpine(root, path) {
|
|
908
989
|
const result = { ...root };
|
|
909
990
|
let target = result;
|
|
910
|
-
for (let
|
|
911
|
-
const key = path[
|
|
912
|
-
const child = cloneContainer(Array.isArray(target) ? target[Number(key)] : target[key], path[
|
|
991
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
992
|
+
const key = path[i];
|
|
993
|
+
const child = cloneContainer(Array.isArray(target) ? target[Number(key)] : target[key], path[i + 1]);
|
|
913
994
|
if (Array.isArray(target)) target[Number(key)] = child;
|
|
914
995
|
else target[key] = child;
|
|
915
996
|
target = child;
|
|
@@ -921,8 +1002,45 @@ window.__ModuleLoader__.load({
|
|
|
921
1002
|
};
|
|
922
1003
|
}
|
|
923
1004
|
/**
|
|
1005
|
+
* Immutably set a nested value, materializing missing intermediate containers.
|
|
1006
|
+
* @param root - draft root (never mutated).
|
|
1007
|
+
* @param path - non-empty key path.
|
|
1008
|
+
* @param value - value to store at the path.
|
|
1009
|
+
* @returns the new draft root.
|
|
1010
|
+
* @throws when `path` is empty.
|
|
1011
|
+
*/
|
|
1012
|
+
function setPath(root, path, value) {
|
|
1013
|
+
if (path.length === 0) throw new Error("schema-form: setPath needs a non-empty path");
|
|
1014
|
+
const { result, parent, leaf } = cloneSpine(root, path);
|
|
1015
|
+
if (Array.isArray(parent)) parent[Number(leaf)] = value;
|
|
1016
|
+
else parent[leaf] = value;
|
|
1017
|
+
return result;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Immutably remove a nested key (the per-field reset: the resolved value
|
|
1021
|
+
* falls back to the composition base and schema defaults). Removing along a
|
|
1022
|
+
* missing branch returns the root unchanged.
|
|
1023
|
+
* @param root - draft root (never mutated).
|
|
1024
|
+
* @param path - non-empty key path.
|
|
1025
|
+
* @returns the new draft root.
|
|
1026
|
+
* @throws when `path` is empty.
|
|
1027
|
+
*/
|
|
1028
|
+
function deletePath(root, path) {
|
|
1029
|
+
if (path.length === 0) throw new Error("schema-form: deletePath needs a non-empty path");
|
|
1030
|
+
if (!hasPath(root, path)) return root;
|
|
1031
|
+
const { result, parent, leaf } = cloneSpine(root, path);
|
|
1032
|
+
if (Array.isArray(parent)) parent.splice(Number(leaf), 1);
|
|
1033
|
+
else Reflect.deleteProperty(parent, leaf);
|
|
1034
|
+
return result;
|
|
1035
|
+
}
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region lib/types/client/schema.js
|
|
1038
|
+
/** Synchronous schema introspection and immutable settings-draft edits. */
|
|
1039
|
+
/**
|
|
924
1040
|
* Settings-owned synchronous schema service. Dynamic client plugins receive
|
|
925
|
-
* this Cordis entity instead of importing executable helpers from one another
|
|
1041
|
+
* this Cordis entity instead of importing executable helpers from one another;
|
|
1042
|
+
* it delegates to the shared schema/draft model layer so one implementation
|
|
1043
|
+
* backs both this service and the shipped editors that import it directly.
|
|
926
1044
|
*/
|
|
927
1045
|
var SettingsSchemaService = class extends _qilin_agent_kylin.Service {
|
|
928
1046
|
/** @param ctx - providing ui-settings context. */
|
|
@@ -935,7 +1053,7 @@ window.__ModuleLoader__.load({
|
|
|
935
1053
|
* @returns live schema node.
|
|
936
1054
|
*/
|
|
937
1055
|
rehydrate(serialized) {
|
|
938
|
-
return
|
|
1056
|
+
return rehydrateSchema(serialized);
|
|
939
1057
|
}
|
|
940
1058
|
/**
|
|
941
1059
|
* Validate a settings draft.
|
|
@@ -944,12 +1062,7 @@ window.__ModuleLoader__.load({
|
|
|
944
1062
|
* @returns validation failure text, or `undefined` when valid.
|
|
945
1063
|
*/
|
|
946
1064
|
validate(schema, draft) {
|
|
947
|
-
|
|
948
|
-
schema(draft);
|
|
949
|
-
return;
|
|
950
|
-
} catch (error) {
|
|
951
|
-
return error instanceof Error ? error.message : String(error);
|
|
952
|
-
}
|
|
1065
|
+
return validateDraft(schema, draft);
|
|
953
1066
|
}
|
|
954
1067
|
/**
|
|
955
1068
|
* Resolve an object, dict, or array schema node at a settings path.
|
|
@@ -958,14 +1071,7 @@ window.__ModuleLoader__.load({
|
|
|
958
1071
|
* @returns the resolved node, or `undefined` when the path is absent.
|
|
959
1072
|
*/
|
|
960
1073
|
nodeAtPath(root, path) {
|
|
961
|
-
|
|
962
|
-
for (const key of path) {
|
|
963
|
-
if (node === void 0) return void 0;
|
|
964
|
-
if (node.type === "object") node = node.dict?.[key];
|
|
965
|
-
else if (node.type === "dict" || node.type === "array") node = node.inner;
|
|
966
|
-
else return void 0;
|
|
967
|
-
}
|
|
968
|
-
return node;
|
|
1074
|
+
return nodeAtPath(root, path);
|
|
969
1075
|
}
|
|
970
1076
|
/**
|
|
971
1077
|
* Read a nested value by a string-key or array-index path.
|
|
@@ -974,16 +1080,7 @@ window.__ModuleLoader__.load({
|
|
|
974
1080
|
* @returns the resolved value, or `undefined` when the path is absent.
|
|
975
1081
|
*/
|
|
976
1082
|
getPath(value, path) {
|
|
977
|
-
|
|
978
|
-
for (const key of path) {
|
|
979
|
-
if (Array.isArray(current)) {
|
|
980
|
-
current = current[Number(key)];
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
|
-
if (typeof current !== "object" || current === null) return void 0;
|
|
984
|
-
current = current[key];
|
|
985
|
-
}
|
|
986
|
-
return current;
|
|
1083
|
+
return getPath(value, path);
|
|
987
1084
|
}
|
|
988
1085
|
/**
|
|
989
1086
|
* Report whether the final path key exists independently of its value.
|
|
@@ -992,12 +1089,7 @@ window.__ModuleLoader__.load({
|
|
|
992
1089
|
* @returns whether the path exists.
|
|
993
1090
|
*/
|
|
994
1091
|
hasPath(value, path) {
|
|
995
|
-
|
|
996
|
-
const parent = this.getPath(value, path.slice(0, -1));
|
|
997
|
-
const key = path[path.length - 1];
|
|
998
|
-
if (Array.isArray(parent)) return Number(key) < parent.length;
|
|
999
|
-
if (typeof parent !== "object" || parent === null) return false;
|
|
1000
|
-
return key in parent;
|
|
1092
|
+
return hasPath(value, path);
|
|
1001
1093
|
}
|
|
1002
1094
|
/**
|
|
1003
1095
|
* Immutably set a nested value, materializing missing containers.
|
|
@@ -1008,11 +1100,7 @@ window.__ModuleLoader__.load({
|
|
|
1008
1100
|
* @throws when `path` is empty.
|
|
1009
1101
|
*/
|
|
1010
1102
|
setPath(root, path, value) {
|
|
1011
|
-
|
|
1012
|
-
const { result, parent, leaf } = cloneSpine(root, path);
|
|
1013
|
-
if (Array.isArray(parent)) parent[Number(leaf)] = value;
|
|
1014
|
-
else parent[leaf] = value;
|
|
1015
|
-
return result;
|
|
1103
|
+
return setPath(root, path, value);
|
|
1016
1104
|
}
|
|
1017
1105
|
/**
|
|
1018
1106
|
* Immutably remove a nested key, preserving an unchanged missing root.
|
|
@@ -1022,12 +1110,7 @@ window.__ModuleLoader__.load({
|
|
|
1022
1110
|
* @throws when `path` is empty.
|
|
1023
1111
|
*/
|
|
1024
1112
|
deletePath(root, path) {
|
|
1025
|
-
|
|
1026
|
-
if (!this.hasPath(root, path)) return root;
|
|
1027
|
-
const { result, parent, leaf } = cloneSpine(root, path);
|
|
1028
|
-
if (Array.isArray(parent)) parent.splice(Number(leaf), 1);
|
|
1029
|
-
else Reflect.deleteProperty(parent, leaf);
|
|
1030
|
-
return result;
|
|
1113
|
+
return deletePath(root, path);
|
|
1031
1114
|
}
|
|
1032
1115
|
};
|
|
1033
1116
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -5,16 +5,18 @@ import z from "@qilin-agent/schemastery";
|
|
|
5
5
|
const DEVELOPER_TOOLS_NAMESPACE = "ui-settings";
|
|
6
6
|
/** New installations and missing values enable the full interface. */
|
|
7
7
|
const DeveloperToolsSettingsFields = { enabled: z.boolean().default(true) };
|
|
8
|
-
|
|
9
|
-
const DeveloperToolsSettingsSchema = z.object({ enabled: DeveloperToolsSettingsFields["enabled"] });
|
|
8
|
+
z.object({ enabled: DeveloperToolsSettingsFields["enabled"] });
|
|
10
9
|
//#endregion
|
|
11
10
|
//#region lib/types/index.js
|
|
12
|
-
/**
|
|
13
|
-
|
|
11
|
+
/** Live preferences projected to the browser. */
|
|
12
|
+
const Config = z.object({ enabled: DeveloperToolsSettingsFields["enabled"].volatile() });
|
|
13
|
+
/** Host preferences are consumed through the configuration form projection.
|
|
14
|
+
* @param ctx Plugin context used for optional settings presentation.
|
|
15
|
+
*/
|
|
14
16
|
function apply(ctx) {
|
|
15
|
-
ctx.inject(["settings"], (
|
|
16
|
-
|
|
17
|
+
ctx.inject(["settings"], (child) => {
|
|
18
|
+
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
17
19
|
});
|
|
18
20
|
}
|
|
19
21
|
//#endregion
|
|
20
|
-
export { DEVELOPER_TOOLS_NAMESPACE, apply };
|
|
22
|
+
export { Config, DEVELOPER_TOOLS_NAMESPACE, apply };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/** Synchronous schema introspection and immutable settings-draft edits. */
|
|
2
2
|
import { Service } from '@qilin-agent/kylin';
|
|
3
3
|
import type { Context } from '@qilin-agent/kylin';
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
export type SchemaNode = Schema;
|
|
4
|
+
import { type SchemaNode } from '@qilin-agent/client-schema-form';
|
|
5
|
+
export type { SchemaNode };
|
|
7
6
|
/**
|
|
8
7
|
* Settings-owned synchronous schema service. Dynamic client plugins receive
|
|
9
|
-
* this Cordis entity instead of importing executable helpers from one another
|
|
8
|
+
* this Cordis entity instead of importing executable helpers from one another;
|
|
9
|
+
* it delegates to the shared schema/draft model layer so one implementation
|
|
10
|
+
* backs both this service and the shipped editors that import it directly.
|
|
10
11
|
*/
|
|
11
12
|
export declare class SettingsSchemaService extends Service {
|
|
12
13
|
/** @param ctx - providing ui-settings context. */
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import type { Volatile, Context } from '@qilin-agent/kylin';
|
|
2
|
+
import z from '@qilin-agent/schemastery';
|
|
3
3
|
export { DEVELOPER_TOOLS_NAMESPACE, type DeveloperToolsSettings } from './developer-tools-settings.ts';
|
|
4
|
-
/**
|
|
4
|
+
/** Runtime preferences projected to the browser. */
|
|
5
|
+
export interface Config {
|
|
6
|
+
/** Whether developer tools are enabled. */
|
|
7
|
+
enabled: Volatile<boolean>;
|
|
8
|
+
}
|
|
9
|
+
/** Live preferences projected to the browser. */
|
|
10
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
11
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
12
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
13
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
14
|
+
}>>, "plain">;
|
|
15
|
+
/** Host preferences are consumed through the configuration form projection.
|
|
16
|
+
* @param ctx Plugin context used for optional settings presentation.
|
|
17
|
+
*/
|
|
5
18
|
export declare function apply(ctx: Context): void;
|
|
6
19
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilin-agent/client-ui-settings",
|
|
3
3
|
"description": "Settings domain base plugin: the settings-namespace scope service and the canonical settings slot-type contract",
|
|
4
|
-
"version": "3.1.
|
|
4
|
+
"version": "3.1.6",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -38,18 +38,19 @@
|
|
|
38
38
|
},
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@qilin-agent/kylin": "~4.0.
|
|
41
|
+
"@qilin-agent/kylin": "~4.0.5"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/react": "~18.3.1",
|
|
45
45
|
"react": "^18.2.0",
|
|
46
|
-
"@qilin-agent/
|
|
47
|
-
"@qilin-agent/
|
|
48
|
-
"@qilin-agent/
|
|
49
|
-
"@qilin-agent/client-
|
|
50
|
-
"@qilin-agent/
|
|
51
|
-
"@qilin-agent/client-
|
|
52
|
-
"@qilin-agent/
|
|
46
|
+
"@qilin-agent/api-remotes": "3.1.6",
|
|
47
|
+
"@qilin-agent/client-schema-form": "3.1.6",
|
|
48
|
+
"@qilin-agent/kylin": "~4.0.5",
|
|
49
|
+
"@qilin-agent/client-test-runtime": "3.1.6",
|
|
50
|
+
"@qilin-agent/client-store": "3.1.6",
|
|
51
|
+
"@qilin-agent/client-ui-slots": "3.1.6",
|
|
52
|
+
"@qilin-agent/settings": "3.1.6",
|
|
53
|
+
"@qilin-agent/util-values": "3.1.6"
|
|
53
54
|
},
|
|
54
55
|
"files": [
|
|
55
56
|
"lib/index.js",
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
"lib/types/**/*.d.ts"
|
|
58
59
|
],
|
|
59
60
|
"dependencies": {
|
|
60
|
-
"@qilin-agent/schemastery": "~3.18.
|
|
61
|
+
"@qilin-agent/schemastery": "~3.18.5"
|
|
61
62
|
},
|
|
62
63
|
"scripts": {
|
|
63
64
|
"bundle": "tsdown",
|