@shwfed/config 2.7.7 → 2.7.8
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/dist/mcp.mjs +92 -31
- package/dist/module.json +1 -1
- package/dist/preview/assets/{badge-B0tiCpa_.js → badge-KAEXz3VO.js} +1 -1
- package/dist/preview/assets/{config-CGvnv-5x.js → config-BXx5syNf.js} +1 -1
- package/dist/preview/assets/{config-jDPbLgBr.js → config-Bb9Yeh33.js} +1 -1
- package/dist/preview/assets/{config-Dafqx9xC.js → config-C-QRPeN1.js} +1 -1
- package/dist/preview/assets/{config-DDPihojt.js → config-CkKx7sVR.js} +1 -1
- package/dist/preview/assets/{config-C-XJ-8Rs.js → config-CtbYlZCL.js} +1 -1
- package/dist/preview/assets/{config-GCvXe12z.js → config-DPlbFBRi.js} +1 -1
- package/dist/preview/assets/{config-BG9TRQcv.js → config-DbfJWa8K.js} +1 -1
- package/dist/preview/assets/{config-DqMRy1WL.js → config-DbirfZyy.js} +1 -1
- package/dist/preview/assets/{config-Bfb2sH6S.js → config-ZczGik30.js} +1 -1
- package/dist/preview/assets/{definition.vue_vue_type_script_setup_true_lang-DBXfCj4Q.js → definition.vue_vue_type_script_setup_true_lang-CQ6MUPKO.js} +1 -1
- package/dist/preview/assets/{index-D7jDE3kp.js → index-Bv_aA34a.js} +1 -1
- package/dist/preview/assets/{index-B0PL01fm.css → index-C9P-6gZd.css} +1 -1
- package/dist/preview/assets/index-CJFU9znN.js +1 -0
- package/dist/preview/assets/{index-CHzOsSTW.js → index-DUOkekYu.js} +162 -162
- package/dist/preview/assets/{item-DV-Garrg.js → item-SC0WQMVu.js} +1 -1
- package/dist/preview/assets/{runtime-DHTqFEQI.js → runtime-BPOf7Yqz.js} +1 -1
- package/dist/preview/assets/{runtime-BovPWken.js → runtime-CC2caFS9.js} +1 -1
- package/dist/preview/assets/{runtime-DKtlQWwc.js → runtime-CLaRFZzt.js} +1 -1
- package/dist/preview/assets/{runtime-D-LBi56N.js → runtime-Ckuz5Kxm.js} +1 -1
- package/dist/preview/assets/{runtime-CwmJ9MLQ.js → runtime-CnKlH0mi.js} +1 -1
- package/dist/preview/assets/{runtime-Cyjx6haa.js → runtime-DO0anKbw.js} +1 -1
- package/dist/preview/assets/{runtime-DA77AmOP.js → runtime-DcStOiOi.js} +1 -1
- package/dist/preview/assets/{runtime-UmLaEUGf.js → runtime-EVgYW6_7.js} +1 -1
- package/dist/preview/assets/{runtime-BxBBFFHA.js → runtime-i32sR7d3.js} +1 -1
- package/dist/preview/index.html +2 -2
- package/dist/runtime/components/block-layout-editor/index.vue +1 -4
- package/dist/runtime/components/config/blocks/2026-05-17/com.shwfed.block.chart.xy/config.vue +5 -0
- package/dist/runtime/components/form/config.vue +24 -16
- package/dist/runtime/components/form/schema.d.ts +1 -1
- package/dist/runtime/components/form/schema.js +6 -1
- package/dist/runtime/components/form/utils/schema-meta.d.ts +2 -0
- package/dist/runtime/components/form/utils/schema-meta.js +24 -0
- package/dist/runtime/components/table/config.vue +0 -36
- package/dist/runtime/components/ui/expression-editor/ExpressionEditor.d.vue.ts +4 -3
- package/dist/runtime/components/ui/expression-editor/ExpressionEditor.vue +135 -111
- package/dist/runtime/components/ui/expression-editor/ExpressionEditor.vue.d.ts +4 -3
- package/dist/runtime/share/expression.d.ts +23 -0
- package/dist/runtime/share/expression.js +37 -17
- package/dist/runtime/vendor/cel-js/CLAUDE.md +2 -2
- package/dist/runtime/vendor/cel-js/PROMPT.md +15 -6
- package/dist/runtime/vendor/cel-js/lib/macros.js +66 -14
- package/package.json +1 -1
- package/dist/preview/assets/index-CSfKAdi7.js +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { Icon } from "@iconify/vue";
|
|
3
3
|
import { computed, ref, useSlots, useTemplateRef, watch } from "vue";
|
|
4
|
+
import { buildCheckEnvironment, evaluateExpression } from "../../../share/expression";
|
|
4
5
|
import { injectCELContext, useScopeAncestry } from "../../../utils/cel-context";
|
|
5
6
|
import { Markdown } from "../markdown";
|
|
6
7
|
import CodeMirrorInput from "./CodeMirrorInput.vue";
|
|
@@ -17,7 +18,8 @@ const props = defineProps({
|
|
|
17
18
|
multiline: { type: Boolean, required: false },
|
|
18
19
|
placeholder: { type: String, required: false },
|
|
19
20
|
resultType: { type: [String, Array], required: false },
|
|
20
|
-
extraVars: { type: Object, required: false }
|
|
21
|
+
extraVars: { type: Object, required: false },
|
|
22
|
+
unlistedVarsAreDyn: { type: Boolean, required: false }
|
|
21
23
|
});
|
|
22
24
|
const emits = defineEmits(["update:modelValue"]);
|
|
23
25
|
const celContext = injectCELContext();
|
|
@@ -26,6 +28,20 @@ const varEntries = computed(() => buildVarEntries(celContext, props.extraVars));
|
|
|
26
28
|
const scopeEntries = computed(() => buildScopeEntries(scopeAncestry.value.slice(1)));
|
|
27
29
|
const scopeLookup = computed(() => buildScopeLookup(scopeAncestry.value));
|
|
28
30
|
const hasVars = computed(() => varEntries.value.length > 0 || scopeEntries.value.length > 0);
|
|
31
|
+
const checkEnvironment = computed(() => {
|
|
32
|
+
const vars = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const [name, entry] of Object.entries(celContext)) vars.set(name, entry.type);
|
|
34
|
+
for (const [name, spec] of Object.entries(props.extraVars ?? {})) vars.set(name, spec.type);
|
|
35
|
+
return buildCheckEnvironment(
|
|
36
|
+
[...vars].map(([name, type]) => ({ name, type })),
|
|
37
|
+
{ unlistedVariablesAreDyn: props.unlistedVarsAreDyn }
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
const validationError = computed(() => {
|
|
41
|
+
const value = props.modelValue ?? props.defaultValue ?? "";
|
|
42
|
+
if (value.trim() === "") return null;
|
|
43
|
+
return evaluateExpression(checkEnvironment.value, value, props.resultType);
|
|
44
|
+
});
|
|
29
45
|
const open = ref(false);
|
|
30
46
|
const entries = ref([]);
|
|
31
47
|
const shownVars = computed(() => entries.value.filter((e) => e.group === "var"));
|
|
@@ -60,120 +76,128 @@ const addonAlign = computed(
|
|
|
60
76
|
</script>
|
|
61
77
|
|
|
62
78
|
<template>
|
|
63
|
-
<
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<slot name="leading" />
|
|
69
|
-
</InputGroupAddon>
|
|
70
|
-
<CodeMirrorInput
|
|
71
|
-
ref="editor"
|
|
72
|
-
:model-value="props.modelValue"
|
|
73
|
-
:default-value="props.defaultValue"
|
|
74
|
-
:placeholder="props.placeholder"
|
|
75
|
-
:multiline="props.multiline"
|
|
76
|
-
:scope-lookup="scopeLookup"
|
|
77
|
-
:class="props.class"
|
|
78
|
-
@update:model-value="(v) => emits('update:modelValue', v)"
|
|
79
|
-
/>
|
|
80
|
-
<InputGroupAddon
|
|
81
|
-
v-if="showAddon"
|
|
82
|
-
:align="addonAlign"
|
|
83
|
-
class="justify-end gap-1.5"
|
|
84
|
-
>
|
|
85
|
-
<Popover
|
|
86
|
-
v-if="hasVars"
|
|
87
|
-
v-model:open="open"
|
|
79
|
+
<div class="flex flex-col gap-1.5">
|
|
80
|
+
<InputGroup>
|
|
81
|
+
<InputGroupAddon
|
|
82
|
+
v-if="$slots.leading"
|
|
83
|
+
align="inline-start"
|
|
88
84
|
>
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
85
|
+
<slot name="leading" />
|
|
86
|
+
</InputGroupAddon>
|
|
87
|
+
<CodeMirrorInput
|
|
88
|
+
ref="editor"
|
|
89
|
+
:model-value="props.modelValue"
|
|
90
|
+
:default-value="props.defaultValue"
|
|
91
|
+
:placeholder="props.placeholder"
|
|
92
|
+
:multiline="props.multiline"
|
|
93
|
+
:scope-lookup="scopeLookup"
|
|
94
|
+
:class="props.class"
|
|
95
|
+
@update:model-value="(v) => emits('update:modelValue', v)"
|
|
96
|
+
/>
|
|
97
|
+
<InputGroupAddon
|
|
98
|
+
v-if="showAddon"
|
|
99
|
+
:align="addonAlign"
|
|
100
|
+
class="justify-end gap-1.5"
|
|
101
|
+
>
|
|
102
|
+
<Popover
|
|
103
|
+
v-if="hasVars"
|
|
104
|
+
v-model:open="open"
|
|
101
105
|
>
|
|
102
|
-
<
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
<PopoverTrigger as-child>
|
|
107
|
+
<InputGroupButton
|
|
108
|
+
size="icon-xs"
|
|
109
|
+
aria-label="Inspect available variables"
|
|
110
|
+
>
|
|
111
|
+
<Icon icon="fluent:braces-variable-20-regular" />
|
|
112
|
+
</InputGroupButton>
|
|
113
|
+
</PopoverTrigger>
|
|
114
|
+
<PopoverContent
|
|
115
|
+
align="end"
|
|
116
|
+
:side-offset="6"
|
|
117
|
+
class="group/popover w-80 p-0 data-[side=top]:**:data-[slot=command-input-wrapper]:border-t data-[side=top]:**:data-[slot=command-input-wrapper]:border-b-0"
|
|
118
|
+
>
|
|
119
|
+
<Command class="rounded group-data-[side=top]/popover:flex-col-reverse">
|
|
120
|
+
<CommandInput placeholder="搜索可用变量" />
|
|
121
|
+
<CommandList class="max-h-48 overflow-y-auto">
|
|
122
|
+
<CommandEmpty>No variables.</CommandEmpty>
|
|
123
|
+
<CommandGroup>
|
|
124
|
+
<CommandItem
|
|
125
|
+
v-for="entry in shownVars"
|
|
126
|
+
:key="entry.id"
|
|
127
|
+
:value="entry.display"
|
|
128
|
+
class="cursor-pointer gap-2"
|
|
129
|
+
@select="insertVariable(entry.insert)"
|
|
130
|
+
@mouseenter="hoveredName = entry.id"
|
|
131
|
+
@focus="hoveredName = entry.id"
|
|
132
|
+
>
|
|
133
|
+
<Icon
|
|
134
|
+
icon="fluent:braces-variable-20-regular"
|
|
135
|
+
class="size-3.5 shrink-0 text-zinc-400"
|
|
136
|
+
/>
|
|
137
|
+
<span class="font-mono text-xs text-zinc-800">{{ entry.display }}</span>
|
|
138
|
+
<span class="flex-1 truncate text-xs text-zinc-500">{{ entry.label }}</span>
|
|
139
|
+
<span class="rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none">
|
|
140
|
+
{{ entry.type }}
|
|
141
|
+
</span>
|
|
142
|
+
</CommandItem>
|
|
143
|
+
</CommandGroup>
|
|
144
|
+
<CommandGroup
|
|
145
|
+
v-if="shownScopes.length > 0"
|
|
146
|
+
heading="跨层引用"
|
|
115
147
|
>
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
<CommandItem
|
|
149
|
+
v-for="entry in shownScopes"
|
|
150
|
+
:key="entry.id"
|
|
151
|
+
:value="`${entry.display} ${entry.id}`"
|
|
152
|
+
class="cursor-pointer gap-2"
|
|
153
|
+
@select="insertVariable(entry.insert)"
|
|
154
|
+
@mouseenter="hoveredName = entry.id"
|
|
155
|
+
@focus="hoveredName = entry.id"
|
|
156
|
+
>
|
|
157
|
+
<Icon
|
|
158
|
+
icon="fluent:link-20-regular"
|
|
159
|
+
class="size-3.5 shrink-0 text-zinc-400"
|
|
160
|
+
/>
|
|
161
|
+
<span class="flex-1 truncate text-xs text-zinc-700">{{ entry.display }}</span>
|
|
162
|
+
<span class="rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none">
|
|
163
|
+
{{ entry.type }}
|
|
164
|
+
</span>
|
|
165
|
+
</CommandItem>
|
|
166
|
+
</CommandGroup>
|
|
167
|
+
</CommandList>
|
|
168
|
+
<div
|
|
169
|
+
v-if="hoveredEntry"
|
|
170
|
+
class="border-t border-zinc-200 px-3 py-2 group-data-[side=top]/popover:border-t-0 group-data-[side=top]/popover:border-b"
|
|
130
171
|
>
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class="cursor-pointer gap-2"
|
|
136
|
-
@select="insertVariable(entry.insert)"
|
|
137
|
-
@mouseenter="hoveredName = entry.id"
|
|
138
|
-
@focus="hoveredName = entry.id"
|
|
139
|
-
>
|
|
140
|
-
<Icon
|
|
141
|
-
icon="fluent:link-20-regular"
|
|
142
|
-
class="size-3.5 shrink-0 text-zinc-400"
|
|
143
|
-
/>
|
|
144
|
-
<span class="flex-1 truncate text-xs text-zinc-700">{{ entry.display }}</span>
|
|
145
|
-
<span class="rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none">
|
|
146
|
-
{{ entry.type }}
|
|
172
|
+
<div class="flex items-center gap-2">
|
|
173
|
+
<span class="text-xs font-medium text-zinc-700">{{ hoveredEntry.label }}</span>
|
|
174
|
+
<span class="ml-auto shrink-0 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none">
|
|
175
|
+
{{ hoveredEntry.type }}
|
|
147
176
|
</span>
|
|
148
|
-
</
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
>
|
|
155
|
-
<div class="flex items-center gap-2">
|
|
156
|
-
<span class="text-xs font-medium text-zinc-700">{{ hoveredEntry.label }}</span>
|
|
157
|
-
<span class="ml-auto shrink-0 rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none">
|
|
158
|
-
{{ hoveredEntry.type }}
|
|
159
|
-
</span>
|
|
177
|
+
</div>
|
|
178
|
+
<Markdown
|
|
179
|
+
v-if="hoveredDescription"
|
|
180
|
+
:source="hoveredDescription"
|
|
181
|
+
class="prose prose-zinc prose-sm mt-1 text-xs"
|
|
182
|
+
/>
|
|
160
183
|
</div>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
</Command>
|
|
185
|
+
</PopoverContent>
|
|
186
|
+
</Popover>
|
|
187
|
+
<span
|
|
188
|
+
v-if="resultTypeLabel"
|
|
189
|
+
class="rounded bg-purple-100 px-1.5 py-0.5 font-mono text-[10px] leading-none text-purple-700 select-none"
|
|
190
|
+
>
|
|
191
|
+
{{ resultTypeLabel }}
|
|
192
|
+
</span>
|
|
193
|
+
<slot name="trailing" />
|
|
194
|
+
</InputGroupAddon>
|
|
195
|
+
</InputGroup>
|
|
196
|
+
<p
|
|
197
|
+
v-if="validationError"
|
|
198
|
+
class="text-xs text-red-500"
|
|
199
|
+
>
|
|
200
|
+
{{ validationError }}
|
|
201
|
+
</p>
|
|
202
|
+
</div>
|
|
179
203
|
</template>
|
|
@@ -12,12 +12,13 @@ type __VLS_Props = {
|
|
|
12
12
|
placeholder?: string;
|
|
13
13
|
resultType?: string | string[];
|
|
14
14
|
extraVars?: Record<string, VarSpec>;
|
|
15
|
+
unlistedVarsAreDyn?: boolean;
|
|
15
16
|
};
|
|
16
|
-
declare var
|
|
17
|
+
declare var __VLS_13: {}, __VLS_129: {};
|
|
17
18
|
type __VLS_Slots = {} & {
|
|
18
|
-
leading?: (props: typeof
|
|
19
|
+
leading?: (props: typeof __VLS_13) => any;
|
|
19
20
|
} & {
|
|
20
|
-
trailing?: (props: typeof
|
|
21
|
+
trailing?: (props: typeof __VLS_129) => any;
|
|
21
22
|
};
|
|
22
23
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
23
24
|
"update:modelValue": (payload: string) => any;
|
|
@@ -15,6 +15,29 @@ export type ResultType = string | string[] | ((type: string) => boolean);
|
|
|
15
15
|
* type-checker infers as `optional<HttpRequest>`.
|
|
16
16
|
*/
|
|
17
17
|
export declare const HttpRequestResult: ResultType;
|
|
18
|
+
/**
|
|
19
|
+
* Type-check `expression` against `env` and, if `resultType` is given, verify the
|
|
20
|
+
* inferred type satisfies it. Returns a Chinese error message, or `null` when the
|
|
21
|
+
* expression is valid. This is the single chokepoint both the schema validator
|
|
22
|
+
* (`Expression`'s filter) and the live `ExpressionEditor` call, so an inline
|
|
23
|
+
* verdict can never disagree with the save-time decode.
|
|
24
|
+
*/
|
|
25
|
+
export declare function evaluateExpression(env: Environment, expression: string, resultType?: ResultType): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Build a checking `Environment` from a flat list of `{ name, type }` variable
|
|
28
|
+
* specs — the projection an `ExpressionEditor` already holds via its injected CEL
|
|
29
|
+
* context plus `extraVars`. Functions/operators come free from the cloned
|
|
30
|
+
* `globalRegistry`; only variables are host-scoped, so this is all the editor
|
|
31
|
+
* needs to reproduce the schema's validation env. `__scopes__` is registered
|
|
32
|
+
* canonically here (and skipped from `vars`) exactly as `Expression` does, so
|
|
33
|
+
* cross-layer addressing type-checks in the editor too.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildCheckEnvironment(vars: Iterable<{
|
|
36
|
+
name: string;
|
|
37
|
+
type: string;
|
|
38
|
+
}>, options?: {
|
|
39
|
+
unlistedVariablesAreDyn?: boolean;
|
|
40
|
+
}): Environment;
|
|
18
41
|
export declare function Expression(options: {
|
|
19
42
|
configure: (env: Environment) => void;
|
|
20
43
|
resultType?: ResultType;
|
|
@@ -3,9 +3,17 @@ import { Environment } from "../vendor/cel-js/lib/index.js";
|
|
|
3
3
|
import { CelError } from "../vendor/cel-js/lib/errors.js";
|
|
4
4
|
import { Locale } from "./locale.js";
|
|
5
5
|
export const HttpRequestResult = (t) => t === "HttpRequest" || t === "optional<HttpRequest>" || t === "optional<dyn>" || t === "dyn";
|
|
6
|
+
function matchesScalarType(actual, expected) {
|
|
7
|
+
if (expected === "dyn" || actual === "dyn") return true;
|
|
8
|
+
if (actual === expected) return true;
|
|
9
|
+
if (actual.startsWith(`${expected}<`)) return true;
|
|
10
|
+
if (actual === `optional<${expected}>` || actual === "optional<dyn>") return true;
|
|
11
|
+
if (actual.startsWith(`optional<${expected}<`)) return true;
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
6
14
|
function matchesResultType(actual, expected) {
|
|
7
|
-
if (typeof expected === "string") return
|
|
8
|
-
if (Array.isArray(expected)) return expected.
|
|
15
|
+
if (typeof expected === "string") return matchesScalarType(actual, expected);
|
|
16
|
+
if (Array.isArray(expected)) return expected.some((e) => matchesScalarType(actual, e));
|
|
9
17
|
return expected(actual);
|
|
10
18
|
}
|
|
11
19
|
function formatExpectedType(expected) {
|
|
@@ -19,6 +27,32 @@ function registerScopeAddressVar(env) {
|
|
|
19
27
|
description: "\u6309 UUID \u5BFB\u5740\u88AB\u906E\u853D\u7684\u7956\u5148\u4F5C\u7528\u57DF"
|
|
20
28
|
});
|
|
21
29
|
}
|
|
30
|
+
export function evaluateExpression(env, expression, resultType) {
|
|
31
|
+
const result = env.check(expression);
|
|
32
|
+
if (Either.isLeft(result)) {
|
|
33
|
+
const error = result.left;
|
|
34
|
+
const summary = error instanceof CelError ? error.summary : String(error);
|
|
35
|
+
return `\u8868\u8FBE\u5F0F\u65E0\u6548\uFF1A${summary}`;
|
|
36
|
+
}
|
|
37
|
+
if (resultType) {
|
|
38
|
+
const actualType = result.right;
|
|
39
|
+
if (!matchesResultType(actualType, resultType)) {
|
|
40
|
+
return `\u8868\u8FBE\u5F0F\u8FD4\u56DE\u7C7B\u578B\u4E0D\u5339\u914D\uFF0C\u671F\u671B ${formatExpectedType(resultType)}\uFF0C\u5B9E\u9645\u4E3A ${actualType}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
export function buildCheckEnvironment(vars, options) {
|
|
46
|
+
const env = new Environment({
|
|
47
|
+
unlistedVariablesAreDyn: options?.unlistedVariablesAreDyn ?? false
|
|
48
|
+
});
|
|
49
|
+
registerScopeAddressVar(env);
|
|
50
|
+
for (const { name, type } of vars) {
|
|
51
|
+
if (name === "__scopes__") continue;
|
|
52
|
+
env.registerVariable(name, type);
|
|
53
|
+
}
|
|
54
|
+
return env;
|
|
55
|
+
}
|
|
22
56
|
function buildDescription(baseline, env, resultType) {
|
|
23
57
|
const defs = env.getDefinitions();
|
|
24
58
|
const baselineVarNames = new Set(baseline.variables.map((v) => v.name));
|
|
@@ -52,21 +86,7 @@ export function Expression(options) {
|
|
|
52
86
|
const description = buildDescription(baseline, env, options.resultType);
|
|
53
87
|
return Schema.String.pipe(
|
|
54
88
|
Schema.filter(
|
|
55
|
-
(expression) =>
|
|
56
|
-
const result = env.check(expression);
|
|
57
|
-
if (Either.isLeft(result)) {
|
|
58
|
-
const error = result.left;
|
|
59
|
-
const summary = error instanceof CelError ? error.summary : String(error);
|
|
60
|
-
return `\u8868\u8FBE\u5F0F\u65E0\u6548\uFF1A${summary}`;
|
|
61
|
-
}
|
|
62
|
-
if (options.resultType) {
|
|
63
|
-
const actualType = result.right;
|
|
64
|
-
if (!matchesResultType(actualType, options.resultType)) {
|
|
65
|
-
return `\u8868\u8FBE\u5F0F\u8FD4\u56DE\u7C7B\u578B\u4E0D\u5339\u914D\uFF0C\u671F\u671B ${formatExpectedType(options.resultType)}\uFF0C\u5B9E\u9645\u4E3A ${actualType}`;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return true;
|
|
69
|
-
},
|
|
89
|
+
(expression) => evaluateExpression(env, expression, options.resultType) ?? true,
|
|
70
90
|
{
|
|
71
91
|
title: "CEL Expression",
|
|
72
92
|
description
|
|
@@ -18,7 +18,7 @@ A `range()` built-in has been added (not from upstream) — `range(number, numbe
|
|
|
18
18
|
|
|
19
19
|
The `homogeneousAggregateLiterals` and `enableOptionalTypes` environment options have been removed. Aggregate data structures (lists, maps) are always heterogeneous — mixed-type literals are inferred as `list<dyn>` or `map<dyn, dyn>`. Optional types (`.?`, `[?]`, `optional.*` helpers) are always enabled.
|
|
20
20
|
|
|
21
|
-
The custom `Optional` class has been replaced with Effect's `Option` type (`import { Option } from 'effect'`). Internal helpers `optionalOf(value)` and `optionalValue(opt)` in `optional.js` handle the CEL-specific semantics (treating `undefined` as `None`, throwing `EvaluationError` on missing value access). The optional surface is `optional.of(A): optional<A>` (the lift / `pure`), `optional.none()`, `optional.hasValue()`, `optional.value()`, `optional.or(optional)`, `optional.orValue(value)`, plus `optional<A>.flatMap(var, body): optional<B>`
|
|
21
|
+
The custom `Optional` class has been replaced with Effect's `Option` type (`import { Option } from 'effect'`). Internal helpers `optionalOf(value)` and `optionalValue(opt)` in `optional.js` handle the CEL-specific semantics (treating `undefined` as `None`, throwing `EvaluationError` on missing value access). The optional surface is `optional.of(A): optional<A>` (the lift / `pure`), `optional.none()`, `optional.hasValue()`, `optional.value()`, `optional.or(optional)`, `optional.orValue(value)`, plus `optional<A>.flatMap(var, body): optional<B>` (monadic bind) and `optional<A>.map(var, body): optional<B>` (the Functor `fmap`) — all local additions (not from upstream). Both are variable-binding macros (registered with `(ast, ast)` arg types so the body stays unevaluated): on `Some` they bind the value and evaluate `body`; on `None` they short-circuit without touching `body`. `flatMap`'s body must itself yield an `optional`; `map`'s body is a plain value that gets re-wrapped in `Some`. `map` is the derived `o.flatMap(v, optional.of(body))` promoted to a first-class macro. NOTE: `map` is **registered in `macros.ts`**, not here — the parser resolves a macro by name before types are known, so the optional `map` and the list-comprehension `map` must share one expander that dispatches on the receiver type at type-check (an `optional` receiver → fmap; `list`/`map` → comprehension; a `dyn` receiver is deferred to a runtime `Option.isOption` check).
|
|
22
22
|
|
|
23
23
|
An `http` built-in has been added (`http-builtins.ts`, `http-builder.ts`) — not from upstream. It registers the `http` constant and the `http` / `HttpRequest` types on `globalRegistry`, so `http.get(url).header(...).body(...)` expressions type-check and evaluate in every `Environment`. A CEL expression only builds an `HttpRequestBuilder` — a pure description of a request; it never issues one. Both terminal methods, `.json()` and `.file()`, are dispatched by the host on the returned builder (neither is a CEL method), so expression evaluation stays free of IO. Endpoints must be absolute URLs — there is no base-URL resolution. Depends on `fx-fetch`. The host can register process-wide default headers via `HttpRequestBuilder.setDefaultHeader(name, valueOrGetter)` / `clearDefaultHeader(name)`; they are merged in at `#buildRequest()` time, with case-insensitive precedence to an explicit `.header(...)` on the builder. A getter that returns `null` / `undefined` / `''` skips the header for that request, so the host can source values from live refs (e.g. the active i18n locale for `Accept-Language`) without baking in stale snapshots.
|
|
24
24
|
|
|
@@ -41,7 +41,7 @@ lib/
|
|
|
41
41
|
├── operators.ts — Binary/unary operator implementations
|
|
42
42
|
├── overloads.ts — Operator overload definitions
|
|
43
43
|
├── functions.ts — Built-in functions + Date type (TZDate)
|
|
44
|
-
├── macros.ts — map, filter, all, exists, exists_one, find, has, cel.bind
|
|
44
|
+
├── macros.ts — map (list comprehension + optional fmap), filter, all, exists, exists_one, find, has, cel.bind
|
|
45
45
|
├── http-builtins.ts — `http` constant + http/HttpRequest types (local addition)
|
|
46
46
|
├── http-builder.ts — `HttpRequestBuilder` — what an http.* expression returns
|
|
47
47
|
├── json-builtins.ts — `JSON` constant + `JSON.parse` / `JSON.stringify` (local addition)
|
|
@@ -316,18 +316,27 @@ o.orValue(default) // unwrap, or `default` when absent
|
|
|
316
316
|
o.or(otherOptional) // first present optional wins
|
|
317
317
|
```
|
|
318
318
|
|
|
319
|
-
###
|
|
320
|
-
`o.
|
|
321
|
-
|
|
319
|
+
### map — transform a present value
|
|
320
|
+
`o.map(v, body)` binds the present value to `v`, evaluates `body` (a plain value),
|
|
321
|
+
and re-wraps the result as present, only when `o` is present; an absent `o`
|
|
322
322
|
short-circuits to `None` and `body` is never evaluated.
|
|
323
323
|
```cel
|
|
324
|
-
loc.query("id").
|
|
324
|
+
loc.query("id").map(v, "user-" + v).orValue("anon")
|
|
325
325
|
// "user-42" when ?id=42 is present; "anon" when absent
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### flatMap — chain a computation that may itself be absent
|
|
329
|
+
`o.flatMap(v, body)` is like `map`, but `body` must itself return an `optional`
|
|
330
|
+
(so it can decide the result is absent). Use `flatMap` when the next step can
|
|
331
|
+
fail; use `map` when it always produces a value.
|
|
332
|
+
```cel
|
|
333
|
+
loc.query("id").flatMap(v, optional.of("user-" + v)).orValue("anon")
|
|
334
|
+
// same as the map example above
|
|
326
335
|
|
|
327
336
|
optional.of(2).flatMap(v, optional.none()).hasValue() // false — body collapsed it
|
|
328
337
|
```
|
|
329
|
-
Together with `optional.of`
|
|
330
|
-
|
|
338
|
+
Together with `optional.of`, `flatMap` is a lawful monad and `map` its derived
|
|
339
|
+
Functor (`o.map(v, body)` == `o.flatMap(v, optional.of(body))`).
|
|
331
340
|
|
|
332
341
|
## Macros
|
|
333
342
|
|
|
@@ -7,27 +7,79 @@ function assertIdentifier(node, message) {
|
|
|
7
7
|
if (node.op === "id") return node.args;
|
|
8
8
|
throw parseError("invalid_macro_argument", message, node);
|
|
9
9
|
}
|
|
10
|
+
function buildMapComprehension(callAst, receiver, iterVar, predicate, transform, invalidMsg, label) {
|
|
11
|
+
let step = transform.clone(OPS.accuPush, transform);
|
|
12
|
+
if (predicate) {
|
|
13
|
+
const accuValue = predicate.clone(OPS.accuValue, void 0);
|
|
14
|
+
step = predicate.clone(OPS.ternary, [predicate.setMeta("label", label), step, accuValue]);
|
|
15
|
+
}
|
|
16
|
+
return callAst.clone(OPS.comprehension, {
|
|
17
|
+
errorsAreFatal: true,
|
|
18
|
+
iterable: receiver,
|
|
19
|
+
iterVarName: assertIdentifier(iterVar, invalidMsg),
|
|
20
|
+
init: callAst.clone(OPS.list, []),
|
|
21
|
+
step,
|
|
22
|
+
result: identity
|
|
23
|
+
});
|
|
24
|
+
}
|
|
10
25
|
function createMapExpander(hasFilter) {
|
|
11
26
|
const functionDesc = hasFilter ? "map(var, filter, transform)" : "map(var, transform)";
|
|
12
27
|
const invalidMsg = `${functionDesc} invalid predicate iteration variable`;
|
|
13
28
|
const label = `${functionDesc} filter predicate must return bool`;
|
|
29
|
+
if (hasFilter) {
|
|
30
|
+
return (opts) => {
|
|
31
|
+
const { args, receiver, ast: callAst } = opts;
|
|
32
|
+
return { callAst: buildMapComprehension(callAst, receiver, args[0], args[1], args[2], invalidMsg, label) };
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function functorMap(ev, macro, ctx, recv) {
|
|
36
|
+
return Effect.gen(function* () {
|
|
37
|
+
if (Option.isNone(recv)) return Option.none();
|
|
38
|
+
const iterCtx = macro.iterCtx.reuse(ctx);
|
|
39
|
+
const result = yield* ev.eval(macro.body, iterCtx.setIterValue(recv.value, ev));
|
|
40
|
+
return Option.some(result);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function typeCheck(chk, macro, ctx) {
|
|
44
|
+
const recvType = chk.check(macro.receiver, ctx);
|
|
45
|
+
if (recvType.kind === "optional") {
|
|
46
|
+
macro.mode = "optional";
|
|
47
|
+
const innerType = recvType.valueType ?? chk.getType("dyn");
|
|
48
|
+
macro.iterCtx = ctx.forkWithVariable(macro.iterVarName, innerType);
|
|
49
|
+
const bodyType = chk.check(macro.body, macro.iterCtx);
|
|
50
|
+
return chk.registry.getOptionalType(bodyType);
|
|
51
|
+
}
|
|
52
|
+
if (recvType.kind === "dyn") {
|
|
53
|
+
macro.mode = "dyn";
|
|
54
|
+
macro.iterCtx = ctx.forkWithVariable(macro.iterVarName, chk.getType("dyn"));
|
|
55
|
+
chk.check(macro.comprehension, ctx);
|
|
56
|
+
return chk.getType("dyn");
|
|
57
|
+
}
|
|
58
|
+
macro.mode = "list";
|
|
59
|
+
return chk.check(macro.comprehension, ctx);
|
|
60
|
+
}
|
|
61
|
+
function evaluate(ev, macro, ctx) {
|
|
62
|
+
if (macro.mode === "list") return ev.eval(macro.comprehension, ctx);
|
|
63
|
+
if (macro.mode === "optional") {
|
|
64
|
+
return Effect.flatMap(ev.eval(macro.receiver, ctx), (recv) => functorMap(ev, macro, ctx, recv));
|
|
65
|
+
}
|
|
66
|
+
return Effect.flatMap(
|
|
67
|
+
ev.eval(macro.receiver, ctx),
|
|
68
|
+
(recv) => Option.isOption(recv) ? functorMap(ev, macro, ctx, recv) : ev.eval(macro.comprehension, ctx)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
14
71
|
return (opts) => {
|
|
15
72
|
const { args, receiver, ast: callAst } = opts;
|
|
16
|
-
const [iterVar, predicate, transform] = hasFilter ? args : [args[0], null, args[1]];
|
|
17
|
-
let step = transform.clone(OPS.accuPush, transform);
|
|
18
|
-
if (predicate) {
|
|
19
|
-
const accuValue = predicate.clone(OPS.accuValue, void 0);
|
|
20
|
-
step = predicate.clone(OPS.ternary, [predicate.setMeta("label", label), step, accuValue]);
|
|
21
|
-
}
|
|
22
73
|
return {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
74
|
+
ast: callAst,
|
|
75
|
+
receiver,
|
|
76
|
+
iterVarName: assertIdentifier(args[0], invalidMsg),
|
|
77
|
+
body: args[1],
|
|
78
|
+
comprehension: buildMapComprehension(callAst, receiver, args[0], null, args[1], invalidMsg, label),
|
|
79
|
+
iterCtx: void 0,
|
|
80
|
+
mode: "list",
|
|
81
|
+
typeCheck,
|
|
82
|
+
evaluate
|
|
31
83
|
};
|
|
32
84
|
};
|
|
33
85
|
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b2 as e}from"./index-CHzOsSTW.js";import{b3 as f,b4 as r,b5 as s}from"./index-CHzOsSTW.js";export{f as TableConfig,r as createTableConfig,e as default,s as getColumnTechnicalKey};
|