@shwfed/config 2.2.0 → 2.2.1
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/module.json +1 -1
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.d.vue.ts +2 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue +31 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue.d.ts +2 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/runtime.vue +12 -2
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.d.ts +1 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.js +4 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.d.vue.ts +2 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue +31 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue.d.ts +2 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/runtime.vue +16 -8
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.d.ts +1 -0
- package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.js +4 -0
- package/dist/runtime/plugins/i18n/index.js +8 -2
- package/dist/runtime/vendor/cel-js/CLAUDE.md +3 -1
- package/dist/runtime/vendor/cel-js/PROMPT.md +20 -0
- package/dist/runtime/vendor/cel-js/lib/http-builder.d.ts +2 -0
- package/dist/runtime/vendor/cel-js/lib/http-builder.js +24 -3
- package/dist/runtime/vendor/cel-js/lib/operators.js +139 -11
- package/dist/runtime/vendor/cel-js/lib/parser.d.ts +2 -1
- package/dist/runtime/vendor/cel-js/lib/parser.js +20 -3
- package/dist/runtime/vendor/cel-js/lib/serialize.js +5 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.d.vue.ts
CHANGED
|
@@ -43,6 +43,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
43
43
|
} | undefined;
|
|
44
44
|
readonly precision?: number | undefined;
|
|
45
45
|
readonly roundingMode?: "round" | "floor" | "ceil" | undefined;
|
|
46
|
+
readonly valueAsString?: boolean | undefined;
|
|
46
47
|
}) => any;
|
|
47
48
|
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
48
49
|
"onUpdate:modelValue"?: ((value: {
|
|
@@ -85,6 +86,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
85
86
|
} | undefined;
|
|
86
87
|
readonly precision?: number | undefined;
|
|
87
88
|
readonly roundingMode?: "round" | "floor" | "ceil" | undefined;
|
|
89
|
+
readonly valueAsString?: boolean | undefined;
|
|
88
90
|
}) => any) | undefined;
|
|
89
91
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
90
92
|
declare const _default: typeof __VLS_export;
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
SelectTrigger,
|
|
27
27
|
SelectValue
|
|
28
28
|
} from "../../../../ui/select";
|
|
29
|
+
import { Switch } from "../../../../ui/switch";
|
|
29
30
|
import { getStructFieldDescription, getStructFieldTitle } from "../../../schema";
|
|
30
31
|
import { DEFAULT_FIELD_ORIENTATION, FIELD_ORIENTATION_OPTIONS } from "../../../utils/common";
|
|
31
32
|
import { schema } from "./schema";
|
|
@@ -76,6 +77,14 @@ function onStepChange(v) {
|
|
|
76
77
|
value.value = { ...value.value, step: v };
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
function onValueAsStringChange(next) {
|
|
81
|
+
if (next) {
|
|
82
|
+
value.value = { ...value.value, valueAsString: true };
|
|
83
|
+
} else {
|
|
84
|
+
const { valueAsString: _omit, ...rest } = value.value;
|
|
85
|
+
value.value = rest;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
79
88
|
</script>
|
|
80
89
|
|
|
81
90
|
<template>
|
|
@@ -300,6 +309,28 @@ function onStepChange(v) {
|
|
|
300
309
|
</Field>
|
|
301
310
|
</div>
|
|
302
311
|
|
|
312
|
+
<Field orientation="vertical">
|
|
313
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
314
|
+
<template
|
|
315
|
+
v-if="fieldDescription('valueAsString')"
|
|
316
|
+
#tooltip
|
|
317
|
+
>
|
|
318
|
+
<Markdown
|
|
319
|
+
:source="fieldDescription('valueAsString')"
|
|
320
|
+
block
|
|
321
|
+
class="prose prose-sm prose-zinc"
|
|
322
|
+
/>
|
|
323
|
+
</template>
|
|
324
|
+
{{ fieldTitle("valueAsString") }}
|
|
325
|
+
</FieldLabel>
|
|
326
|
+
<div>
|
|
327
|
+
<Switch
|
|
328
|
+
:model-value="value.valueAsString ?? false"
|
|
329
|
+
@update:model-value="onValueAsStringChange"
|
|
330
|
+
/>
|
|
331
|
+
</div>
|
|
332
|
+
</Field>
|
|
333
|
+
|
|
303
334
|
<div class="grid grid-cols-2 gap-3">
|
|
304
335
|
<Field orientation="vertical">
|
|
305
336
|
<FieldLabel class="text-xs text-zinc-500">
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
43
43
|
} | undefined;
|
|
44
44
|
readonly precision?: number | undefined;
|
|
45
45
|
readonly roundingMode?: "round" | "floor" | "ceil" | undefined;
|
|
46
|
+
readonly valueAsString?: boolean | undefined;
|
|
46
47
|
}) => any;
|
|
47
48
|
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
48
49
|
"onUpdate:modelValue"?: ((value: {
|
|
@@ -85,6 +86,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
85
86
|
} | undefined;
|
|
86
87
|
readonly precision?: number | undefined;
|
|
87
88
|
readonly roundingMode?: "round" | "floor" | "ceil" | undefined;
|
|
89
|
+
readonly valueAsString?: boolean | undefined;
|
|
88
90
|
}) => any) | undefined;
|
|
89
91
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
90
92
|
declare const _default: typeof __VLS_export;
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/runtime.vue
CHANGED
|
@@ -62,8 +62,18 @@ const formatOptions = computed(() => ({
|
|
|
62
62
|
}));
|
|
63
63
|
const { draft, commit } = useFieldValue({
|
|
64
64
|
binding: () => props.config.binding,
|
|
65
|
-
fromState: (raw) =>
|
|
66
|
-
|
|
65
|
+
fromState: (raw) => {
|
|
66
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
67
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
68
|
+
const n = Number(raw);
|
|
69
|
+
return Number.isFinite(n) ? n : void 0;
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
},
|
|
73
|
+
toState: (next) => {
|
|
74
|
+
if (typeof next !== "number" || !Number.isFinite(next)) return null;
|
|
75
|
+
return props.config.valueAsString ? String(next) : next;
|
|
76
|
+
}
|
|
67
77
|
});
|
|
68
78
|
function applyRounding(n, precision, mode) {
|
|
69
79
|
const factor = 10 ** precision;
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
|
|
|
49
49
|
precision: Schema.optional<Schema.refine<number, Schema.filter<typeof Schema.Number>>>;
|
|
50
50
|
roundingMode: Schema.optional<Schema.Literal<["round", "floor", "ceil"]>>;
|
|
51
51
|
step: Schema.optional<Schema.refine<number, typeof Schema.Number>>;
|
|
52
|
+
valueAsString: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
52
53
|
min: Schema.optional<Schema.Schema<string, string, never>>;
|
|
53
54
|
max: Schema.optional<Schema.Schema<string, string, never>>;
|
|
54
55
|
id: Schema.refine<string, typeof Schema.String>;
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.js
CHANGED
|
@@ -55,6 +55,10 @@ export function schema(configure) {
|
|
|
55
55
|
title: "\u6B65\u957F",
|
|
56
56
|
description: "\u53D6\u503C\u7684\u6700\u5C0F\u6B65\u957F\uFF1B\u7559\u7A7A\u65F6\u4E0D\u9650\u5236\uFF08\u4EFB\u610F\u7CBE\u5EA6\uFF09"
|
|
57
57
|
})),
|
|
58
|
+
valueAsString: Schema.optional(Schema.Boolean.annotations({
|
|
59
|
+
title: "\u4EE5\u5B57\u7B26\u4E32\u8BFB\u5199",
|
|
60
|
+
description: "\u5F00\u542F\u540E\u8868\u5355\u72B6\u6001\u4EE5\u5B57\u7B26\u4E32\u5F62\u5F0F\u5B58\u50A8\u8BE5\u503C\uFF08\u9002\u7528\u4E8E\u540E\u7AEF\u4F7F\u7528 `DECIMAL` / `BIGINT` \u7B49\u9700\u8981\u539F\u6837\u56DE\u4F20\u7684\u5B57\u6BB5\uFF09\uFF1B\u5173\u95ED\u65F6\u82E5\u8BFB\u5230\u7684\u662F\u6570\u503C\u5B57\u7B26\u4E32\u4E5F\u4F1A\u88AB\u89E3\u6790\uFF0C\u4F46\u5199\u56DE\u4ECD\u4E3A\u6570\u503C\u3002\u6CE8\u610F\uFF1A\u8F93\u5165\u65F6\u4ECD\u7ECF\u8FC7 JavaScript \u6D6E\u70B9\u6570\uFF0C\u4EC5\u80FD\u5728\u300C\u88C5\u8F7D-\u56DE\u4F20\u300D\u672A\u7F16\u8F91\u573A\u666F\u4E0B\u4FDD\u7559\u7CBE\u5EA6"
|
|
61
|
+
})),
|
|
58
62
|
min: Schema.optional(CelNumber.annotations({
|
|
59
63
|
title: "\u6700\u5C0F\u503C",
|
|
60
64
|
description: "\u5141\u8BB8\u8F93\u5165\u7684\u6700\u5C0F\u503C\u8868\u8FBE\u5F0F\uFF1B\u7559\u7A7A\u65F6\u4E0D\u9650\u5236\u4E0B\u9650"
|
|
@@ -51,6 +51,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
51
51
|
readonly precision?: number | undefined;
|
|
52
52
|
readonly roundingMode?: "round" | "floor" | "ceil" | undefined;
|
|
53
53
|
readonly rangeSeparatorIcon?: string | undefined;
|
|
54
|
+
readonly valueAsString?: boolean | undefined;
|
|
54
55
|
}) => any;
|
|
55
56
|
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
56
57
|
"onUpdate:modelValue"?: ((value: {
|
|
@@ -101,6 +102,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
101
102
|
readonly precision?: number | undefined;
|
|
102
103
|
readonly roundingMode?: "round" | "floor" | "ceil" | undefined;
|
|
103
104
|
readonly rangeSeparatorIcon?: string | undefined;
|
|
105
|
+
readonly valueAsString?: boolean | undefined;
|
|
104
106
|
}) => any) | undefined;
|
|
105
107
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
106
108
|
declare const _default: typeof __VLS_export;
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
SelectTrigger,
|
|
29
29
|
SelectValue
|
|
30
30
|
} from "../../../../ui/select";
|
|
31
|
+
import { Switch } from "../../../../ui/switch";
|
|
31
32
|
import { getStructFieldDescription, getStructFieldTitle } from "../../../schema";
|
|
32
33
|
import { DEFAULT_FIELD_ORIENTATION, FIELD_ORIENTATION_OPTIONS } from "../../../utils/common";
|
|
33
34
|
import { schema } from "./schema";
|
|
@@ -112,6 +113,14 @@ function setSeparatorIcon(next) {
|
|
|
112
113
|
value.value = { ...value.value, rangeSeparatorIcon: trimmed };
|
|
113
114
|
}
|
|
114
115
|
}
|
|
116
|
+
function onValueAsStringChange(next) {
|
|
117
|
+
if (next) {
|
|
118
|
+
value.value = { ...value.value, valueAsString: true };
|
|
119
|
+
} else {
|
|
120
|
+
const { valueAsString: _omit, ...rest } = value.value;
|
|
121
|
+
value.value = rest;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
115
124
|
</script>
|
|
116
125
|
|
|
117
126
|
<template>
|
|
@@ -468,6 +477,28 @@ function setSeparatorIcon(next) {
|
|
|
468
477
|
@update:model-value="setSeparatorIcon"
|
|
469
478
|
/>
|
|
470
479
|
</Field>
|
|
480
|
+
|
|
481
|
+
<Field orientation="vertical">
|
|
482
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
483
|
+
<template
|
|
484
|
+
v-if="fieldDescription('valueAsString')"
|
|
485
|
+
#tooltip
|
|
486
|
+
>
|
|
487
|
+
<Markdown
|
|
488
|
+
:source="fieldDescription('valueAsString')"
|
|
489
|
+
block
|
|
490
|
+
class="prose prose-sm prose-zinc"
|
|
491
|
+
/>
|
|
492
|
+
</template>
|
|
493
|
+
{{ fieldTitle("valueAsString") }}
|
|
494
|
+
</FieldLabel>
|
|
495
|
+
<div>
|
|
496
|
+
<Switch
|
|
497
|
+
:model-value="value.valueAsString ?? false"
|
|
498
|
+
@update:model-value="onValueAsStringChange"
|
|
499
|
+
/>
|
|
500
|
+
</div>
|
|
501
|
+
</Field>
|
|
471
502
|
</div>
|
|
472
503
|
|
|
473
504
|
<div class="grid grid-cols-3 gap-3">
|
|
@@ -51,6 +51,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
51
51
|
readonly precision?: number | undefined;
|
|
52
52
|
readonly roundingMode?: "round" | "floor" | "ceil" | undefined;
|
|
53
53
|
readonly rangeSeparatorIcon?: string | undefined;
|
|
54
|
+
readonly valueAsString?: boolean | undefined;
|
|
54
55
|
}) => any;
|
|
55
56
|
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
56
57
|
"onUpdate:modelValue"?: ((value: {
|
|
@@ -101,6 +102,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
101
102
|
readonly precision?: number | undefined;
|
|
102
103
|
readonly roundingMode?: "round" | "floor" | "ceil" | undefined;
|
|
103
104
|
readonly rangeSeparatorIcon?: string | undefined;
|
|
105
|
+
readonly valueAsString?: boolean | undefined;
|
|
104
106
|
}) => any) | undefined;
|
|
105
107
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
106
108
|
declare const _default: typeof __VLS_export;
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/runtime.vue
CHANGED
|
@@ -63,15 +63,23 @@ const formatOptions = computed(() => ({
|
|
|
63
63
|
useGrouping: false,
|
|
64
64
|
maximumFractionDigits: 20
|
|
65
65
|
}));
|
|
66
|
+
function asNumber(raw) {
|
|
67
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
68
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
69
|
+
const n = Number(raw);
|
|
70
|
+
return Number.isFinite(n) ? n : void 0;
|
|
71
|
+
}
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
66
74
|
function asRange(raw) {
|
|
67
75
|
if (!Array.isArray(raw) || raw.length !== 2) return void 0;
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
if (
|
|
76
|
+
const a = asNumber(raw[0]);
|
|
77
|
+
const b = asNumber(raw[1]);
|
|
78
|
+
if (a == null || b == null) return void 0;
|
|
71
79
|
return [a, b];
|
|
72
80
|
}
|
|
73
|
-
function
|
|
74
|
-
return
|
|
81
|
+
function shape(n) {
|
|
82
|
+
return props.config.valueAsString ? String(n) : n;
|
|
75
83
|
}
|
|
76
84
|
const uncontrolled = ref(void 0);
|
|
77
85
|
const model = computed({
|
|
@@ -91,11 +99,11 @@ const model = computed({
|
|
|
91
99
|
return;
|
|
92
100
|
}
|
|
93
101
|
if (typeof binding === "string") {
|
|
94
|
-
setAt(binding, next
|
|
102
|
+
setAt(binding, next ? [shape(next[0]), shape(next[1])] : null);
|
|
95
103
|
return;
|
|
96
104
|
}
|
|
97
|
-
setAt(binding[0], next
|
|
98
|
-
setAt(binding[1], next
|
|
105
|
+
setAt(binding[0], next ? shape(next[0]) : null);
|
|
106
|
+
setAt(binding[1], next ? shape(next[1]) : null);
|
|
99
107
|
}
|
|
100
108
|
});
|
|
101
109
|
const localStart = ref(model.value?.[0]);
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
|
|
|
56
56
|
precision: Schema.optional<Schema.refine<number, Schema.filter<typeof Schema.Number>>>;
|
|
57
57
|
roundingMode: Schema.optional<Schema.Literal<["round", "floor", "ceil"]>>;
|
|
58
58
|
step: Schema.optional<Schema.refine<number, typeof Schema.Number>>;
|
|
59
|
+
valueAsString: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
|
|
59
60
|
min: Schema.optional<Schema.Schema<string, string, never>>;
|
|
60
61
|
max: Schema.optional<Schema.Schema<string, string, never>>;
|
|
61
62
|
rangeSeparatorIcon: Schema.optional<Schema.refine<string, typeof Schema.String>>;
|
package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.js
CHANGED
|
@@ -66,6 +66,10 @@ export function schema(configure) {
|
|
|
66
66
|
title: "\u6B65\u957F",
|
|
67
67
|
description: "\u53D6\u503C\u7684\u6700\u5C0F\u6B65\u957F\uFF1B\u7559\u7A7A\u65F6\u4E0D\u9650\u5236\uFF08\u4EFB\u610F\u7CBE\u5EA6\uFF09"
|
|
68
68
|
})),
|
|
69
|
+
valueAsString: Schema.optional(Schema.Boolean.annotations({
|
|
70
|
+
title: "\u4EE5\u5B57\u7B26\u4E32\u8BFB\u5199",
|
|
71
|
+
description: "\u5F00\u542F\u540E\u8868\u5355\u72B6\u6001\u4EE5\u5B57\u7B26\u4E32\u5F62\u5F0F\u5B58\u50A8\u4E24\u7AEF\u7684\u503C\uFF08\u9002\u7528\u4E8E\u540E\u7AEF\u4F7F\u7528 `DECIMAL` / `BIGINT` \u7B49\u9700\u8981\u539F\u6837\u56DE\u4F20\u7684\u5B57\u6BB5\uFF09\uFF1B\u5173\u95ED\u65F6\u82E5\u8BFB\u5230\u7684\u662F\u6570\u503C\u5B57\u7B26\u4E32\u4E5F\u4F1A\u88AB\u89E3\u6790\uFF0C\u4F46\u5199\u56DE\u4ECD\u4E3A\u6570\u503C\u3002\u6CE8\u610F\uFF1A\u8F93\u5165\u65F6\u4ECD\u7ECF\u8FC7 JavaScript \u6D6E\u70B9\u6570\uFF0C\u4EC5\u80FD\u5728\u300C\u88C5\u8F7D-\u56DE\u4F20\u300D\u672A\u7F16\u8F91\u573A\u666F\u4E0B\u4FDD\u7559\u7CBE\u5EA6"
|
|
72
|
+
})),
|
|
69
73
|
min: Schema.optional(CelNumber.annotations({
|
|
70
74
|
title: "\u6700\u5C0F\u503C",
|
|
71
75
|
description: "\u5141\u8BB8\u8F93\u5165\u7684\u6700\u5C0F\u503C\u8868\u8FBE\u5F0F\uFF1B\u540C\u65F6\u4F5C\u7528\u4E8E\u8D77\u59CB\u4E0E\u7ED3\u675F\u8F93\u5165"
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { defineNuxtPlugin } from "#app";
|
|
2
2
|
import { createI18n } from "vue-i18n";
|
|
3
|
+
import { HttpRequestBuilder } from "../../vendor/cel-js/lib/http-builder.js";
|
|
3
4
|
export default defineNuxtPlugin({
|
|
4
5
|
name: "shwfed:i18n",
|
|
5
6
|
setup: (nuxt) => {
|
|
6
|
-
|
|
7
|
+
const i18n = createI18n({
|
|
7
8
|
locale: navigator?.language,
|
|
8
9
|
legacy: false,
|
|
9
10
|
fallbackWarn: false,
|
|
10
11
|
fallbackLocale: "zh",
|
|
11
12
|
globalInjection: false
|
|
12
|
-
})
|
|
13
|
+
});
|
|
14
|
+
nuxt.vueApp.use(i18n);
|
|
15
|
+
HttpRequestBuilder.setDefaultHeader(
|
|
16
|
+
"Accept-Language",
|
|
17
|
+
() => i18n.global.locale.value
|
|
18
|
+
);
|
|
13
19
|
}
|
|
14
20
|
});
|
|
@@ -20,7 +20,9 @@ The `homogeneousAggregateLiterals` and `enableOptionalTypes` environment options
|
|
|
20
20
|
|
|
21
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).
|
|
22
22
|
|
|
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`.
|
|
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
|
+
|
|
25
|
+
Spread syntax (`...expr`) inside list and map literals — not from upstream. A new `ELLIPSIS` token (3-char lookahead in the lexer) and a `spread` AST op let `[1, ...a, 4]` and `{...a, "x": 1, ...b}` desugar inside the parent literal. The spread node's own `check`/`evaluate` are defensive stubs — `parsePrimary` never produces one, so `...` outside a list/map is an "unexpected token" parse error. List `args` shape is unchanged (`IASTNode[]`, with spread elements detected via `op === 'spread'`); map `args` becomes `([IASTNode, IASTNode] | [IASTNode])[]` — a length-1 tuple represents a spread entry, preserving positional ordering for override semantics (later writes win). The fast path is preserved: the `list`/`map` `check` swaps `evaluate` meta to `evaluateSpreadList`/`evaluateSpreadMap` only when a spread child is present; literals without spreads run the original `resolveAstArray`/`safeFromEntries` paths byte-for-byte. Spread map sources may be plain objects, `Map` instances, or registered message types (typed as `map<string, dyn>`); the `__proto__`/`constructor`/`prototype` skip from `safeFromEntries` is applied to spread sources too. Runtime errors fire on non-list/non-map sources (including `null`). `maxListElements` / `maxMapEntries` count a spread as one entry — runtime expansion bypasses those caps (deliberate trade-off). Call-argument spread (`f(...args)`) is **not** supported.
|
|
24
26
|
|
|
25
27
|
## Architecture
|
|
26
28
|
|
|
@@ -67,6 +67,26 @@ condition ? value_if_true : value_if_false
|
|
|
67
67
|
[1, 2] + [3, 4] // [1, 2, 3, 4]
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
### Spread (`...expr`)
|
|
71
|
+
A spread inlines another list inside a list literal or another map inside a
|
|
72
|
+
map literal. Order is preserved — a later entry (including a later spread)
|
|
73
|
+
overrides an earlier one when keys collide.
|
|
74
|
+
```cel
|
|
75
|
+
[1, ...a, 4] // a flattens in position
|
|
76
|
+
[...a, ...b] // equivalent to a + b
|
|
77
|
+
|
|
78
|
+
{...a, "x": 1} // copy a, then set x to 1
|
|
79
|
+
{"x": 1, ...a} // set x, then let a override
|
|
80
|
+
{...a, ...b} // b overrides a on overlapping keys
|
|
81
|
+
{...a, "x": 1, ...b} // a, then x, then b — last write wins
|
|
82
|
+
|
|
83
|
+
[...[1, 2], 3, ...[4]] // [1, 2, 3, 4]
|
|
84
|
+
```
|
|
85
|
+
Spread is only valid as a list element or map entry — `f(...args)` is not
|
|
86
|
+
supported (CEL resolves overloads by fixed arity). Spreading a non-list into
|
|
87
|
+
a list, or a non-map into a map (including `null`), is an error — at compile
|
|
88
|
+
time when statically known, at runtime when the source is `dyn`.
|
|
89
|
+
|
|
70
90
|
### Field access
|
|
71
91
|
```cel
|
|
72
92
|
obj.field // access field on map/object
|
|
@@ -13,6 +13,8 @@ import { Effect } from 'effect';
|
|
|
13
13
|
import { Fetch } from 'fx-fetch';
|
|
14
14
|
export declare class HttpRequestBuilder {
|
|
15
15
|
#private;
|
|
16
|
+
static setDefaultHeader(name: string, value: string | (() => string | null | undefined)): void;
|
|
17
|
+
static clearDefaultHeader(name: string): void;
|
|
16
18
|
constructor(url: string, method: string);
|
|
17
19
|
header(name: string, value: string): this;
|
|
18
20
|
query(key: string, value: string | number): this;
|
|
@@ -38,6 +38,20 @@ export class HttpRequestBuilder {
|
|
|
38
38
|
#headers = [];
|
|
39
39
|
#queries = [];
|
|
40
40
|
#body;
|
|
41
|
+
// Process-wide defaults applied at `#buildRequest()` time. Each entry is
|
|
42
|
+
// keyed by lowercased header name so an explicit `.header('X', …)` on the
|
|
43
|
+
// builder wins regardless of casing. A getter returning `null` / `undefined`
|
|
44
|
+
// / `''` skips the header for that request — lets the host source values
|
|
45
|
+
// from a live ref (e.g. the current i18n locale) without baking in stale
|
|
46
|
+
// snapshots.
|
|
47
|
+
static #defaultHeaders = /* @__PURE__ */ new Map();
|
|
48
|
+
static setDefaultHeader(name, value) {
|
|
49
|
+
const get = typeof value === "function" ? value : () => value;
|
|
50
|
+
HttpRequestBuilder.#defaultHeaders.set(name.toLowerCase(), { name, get });
|
|
51
|
+
}
|
|
52
|
+
static clearDefaultHeader(name) {
|
|
53
|
+
HttpRequestBuilder.#defaultHeaders.delete(name.toLowerCase());
|
|
54
|
+
}
|
|
41
55
|
constructor(url, method) {
|
|
42
56
|
this.#url = url;
|
|
43
57
|
this.#method = method;
|
|
@@ -117,9 +131,16 @@ export class HttpRequestBuilder {
|
|
|
117
131
|
url: this.#url,
|
|
118
132
|
method: this.#method
|
|
119
133
|
};
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
const explicit = new Set(this.#headers.map(([k]) => k.toLowerCase()));
|
|
135
|
+
const headers = {};
|
|
136
|
+
for (const [k, v] of this.#headers) headers[k] = v;
|
|
137
|
+
for (const [key, { name, get }] of HttpRequestBuilder.#defaultHeaders) {
|
|
138
|
+
if (explicit.has(key)) continue;
|
|
139
|
+
const v = get();
|
|
140
|
+
if (v == null || v === "") continue;
|
|
141
|
+
headers[name] = v;
|
|
142
|
+
}
|
|
143
|
+
if (Object.keys(headers).length > 0) {
|
|
123
144
|
parts.headers = headers;
|
|
124
145
|
}
|
|
125
146
|
if (this.#body !== void 0) {
|
|
@@ -74,8 +74,106 @@ function checkOptionalAccessNode(chk, ast, ctx) {
|
|
|
74
74
|
const actualType = leftType.kind === "optional" ? leftType.valueType : leftType;
|
|
75
75
|
return chk.registry.getOptionalType(chk.checkAccessOnType(ast, ctx, actualType, true));
|
|
76
76
|
}
|
|
77
|
-
function
|
|
78
|
-
return
|
|
77
|
+
function spreadInner(node) {
|
|
78
|
+
return node.args;
|
|
79
|
+
}
|
|
80
|
+
function spreadListElementType(chk, ctx, node) {
|
|
81
|
+
const t = chk.check(spreadInner(node), ctx);
|
|
82
|
+
if (t.kind === "dyn") return chk.dynType;
|
|
83
|
+
if (t.kind === "list") return t.valueType;
|
|
84
|
+
throw chk.createError(
|
|
85
|
+
"invalid_spread",
|
|
86
|
+
`Cannot spread '${chk.formatType(t)}' into a list (expected a list)`,
|
|
87
|
+
node
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
function spreadMapEntryTypes(chk, ctx, node) {
|
|
91
|
+
const t = chk.check(spreadInner(node), ctx);
|
|
92
|
+
if (t.kind === "dyn") return [chk.dynType, chk.dynType];
|
|
93
|
+
if (t.kind === "map") return [t.keyType, t.valueType];
|
|
94
|
+
if (t.kind === "message") {
|
|
95
|
+
const Base2 = chk;
|
|
96
|
+
return [Base2.stringType, chk.dynType];
|
|
97
|
+
}
|
|
98
|
+
throw chk.createError(
|
|
99
|
+
"invalid_spread",
|
|
100
|
+
`Cannot spread '${chk.formatType(t)}' into a map (expected a map)`,
|
|
101
|
+
node
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const SPREAD_SKIP_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
105
|
+
function assignFragmentEntry(obj, k, v) {
|
|
106
|
+
const key = k;
|
|
107
|
+
if (SPREAD_SKIP_KEYS.has(key)) return;
|
|
108
|
+
obj[key] = v;
|
|
109
|
+
}
|
|
110
|
+
function mergeMapFragments(ev, frags) {
|
|
111
|
+
const obj = {};
|
|
112
|
+
for (const f of frags) {
|
|
113
|
+
if ("kv" in f) {
|
|
114
|
+
assignFragmentEntry(obj, f.kv[0], f.kv[1]);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const src = f.spread;
|
|
118
|
+
if (src instanceof Map) {
|
|
119
|
+
for (const [k, v] of src) assignFragmentEntry(obj, k, v);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (src !== null && typeof src === "object" && !isArray(src)) {
|
|
123
|
+
for (const k in src) {
|
|
124
|
+
if (hasOwn(src, k)) assignFragmentEntry(obj, k, src[k]);
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
throw ev.createError(
|
|
129
|
+
"invalid_spread",
|
|
130
|
+
`Cannot spread a non-map value into a map`,
|
|
131
|
+
f.node
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return obj;
|
|
135
|
+
}
|
|
136
|
+
function evaluateSpreadList(ev, ast, ctx) {
|
|
137
|
+
const arr = ast.args;
|
|
138
|
+
return Effect.all(
|
|
139
|
+
arr.map(
|
|
140
|
+
(el) => el.op === "spread" ? ev.eval(spreadInner(el), ctx).pipe(
|
|
141
|
+
Effect.flatMap(
|
|
142
|
+
(v) => isArray(v) ? Effect.succeed(v) : Effect.fail(
|
|
143
|
+
ev.createError(
|
|
144
|
+
"invalid_spread",
|
|
145
|
+
`Cannot spread a non-list value into a list`,
|
|
146
|
+
el
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
) : ev.eval(el, ctx).pipe(Effect.map((v) => [v]))
|
|
151
|
+
)
|
|
152
|
+
).pipe(Effect.map((parts) => parts.flat()));
|
|
153
|
+
}
|
|
154
|
+
function evaluateSpreadMap(ev, ast, ctx) {
|
|
155
|
+
const arr = ast.args;
|
|
156
|
+
return Effect.all(
|
|
157
|
+
arr.map((e) => {
|
|
158
|
+
if (e.length === 1 || e[0].op === "spread") {
|
|
159
|
+
const node = e[0];
|
|
160
|
+
return ev.eval(spreadInner(node), ctx).pipe(
|
|
161
|
+
Effect.map((src) => ({ spread: src, node }))
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const pair = e;
|
|
165
|
+
return Effect.all([ev.eval(pair[0], ctx), ev.eval(pair[1], ctx)]).pipe(
|
|
166
|
+
Effect.map(([k, v]) => ({ kv: [k, v] }))
|
|
167
|
+
);
|
|
168
|
+
})
|
|
169
|
+
).pipe(
|
|
170
|
+
Effect.flatMap(
|
|
171
|
+
(frags) => Effect.try({
|
|
172
|
+
try: () => mergeMapFragments(ev, frags),
|
|
173
|
+
catch: (e) => e
|
|
174
|
+
})
|
|
175
|
+
)
|
|
176
|
+
);
|
|
79
177
|
}
|
|
80
178
|
function ternaryConditionError(ev, value, node) {
|
|
81
179
|
const type = ev.debugRuntimeType(value);
|
|
@@ -481,9 +579,19 @@ const OPERATORS_MAP = {
|
|
|
481
579
|
const arr = ast.args;
|
|
482
580
|
const arrLen = arr.length;
|
|
483
581
|
if (arrLen === 0) return ast.setMeta("evaluate", emptyList) && chk.getType("list<T>");
|
|
484
|
-
let
|
|
485
|
-
|
|
486
|
-
|
|
582
|
+
let hasSpread = false;
|
|
583
|
+
let valueType;
|
|
584
|
+
for (const el of arr) {
|
|
585
|
+
let t;
|
|
586
|
+
if (el.op === "spread") {
|
|
587
|
+
hasSpread = true;
|
|
588
|
+
t = spreadListElementType(chk, ctx, el);
|
|
589
|
+
} else {
|
|
590
|
+
t = chk.check(el, ctx);
|
|
591
|
+
}
|
|
592
|
+
valueType = valueType === void 0 ? t : valueType.unify(chk.registry, t) ?? dynType;
|
|
593
|
+
}
|
|
594
|
+
if (hasSpread) ast.setMeta("evaluate", evaluateSpreadList);
|
|
487
595
|
return chk.registry.getListType(valueType);
|
|
488
596
|
},
|
|
489
597
|
evaluate(ev, ast, ctx) {
|
|
@@ -495,13 +603,23 @@ const OPERATORS_MAP = {
|
|
|
495
603
|
const arr = ast.args;
|
|
496
604
|
const arrLen = arr.length;
|
|
497
605
|
if (arrLen === 0) return ast.setMeta("evaluate", emptyMap) && chk.getType("map<K, V>");
|
|
498
|
-
let
|
|
499
|
-
let
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
606
|
+
let hasSpread = false;
|
|
607
|
+
let keyType;
|
|
608
|
+
let valueType;
|
|
609
|
+
for (const e of arr) {
|
|
610
|
+
let k;
|
|
611
|
+
let v;
|
|
612
|
+
if (e.length === 1 || e[0].op === "spread") {
|
|
613
|
+
hasSpread = true;
|
|
614
|
+
[k, v] = spreadMapEntryTypes(chk, ctx, e[0]);
|
|
615
|
+
} else {
|
|
616
|
+
k = chk.check(e[0], ctx);
|
|
617
|
+
v = chk.check(e[1], ctx);
|
|
618
|
+
}
|
|
619
|
+
keyType = keyType === void 0 ? k : keyType.unify(chk.registry, k) ?? dynType;
|
|
620
|
+
valueType = valueType === void 0 ? v : valueType.unify(chk.registry, v) ?? dynType;
|
|
504
621
|
}
|
|
622
|
+
if (hasSpread) ast.setMeta("evaluate", evaluateSpreadMap);
|
|
505
623
|
return chk.registry.getMapType(keyType, valueType);
|
|
506
624
|
},
|
|
507
625
|
evaluate(ev, ast, ctx) {
|
|
@@ -513,6 +631,16 @@ const OPERATORS_MAP = {
|
|
|
513
631
|
).pipe(Effect.map(safeFromEntries));
|
|
514
632
|
}
|
|
515
633
|
},
|
|
634
|
+
"spread": {
|
|
635
|
+
// Reached only via direct `ast.check`/`ast.evaluate` — parents consume the
|
|
636
|
+
// node inline, so these are defensive guards.
|
|
637
|
+
check(chk, ast) {
|
|
638
|
+
throw chk.createError("misplaced_spread", `'...' is only valid inside a list or map literal`, ast);
|
|
639
|
+
},
|
|
640
|
+
evaluate(ev, ast) {
|
|
641
|
+
return Effect.fail(ev.createError("misplaced_spread", `'...' is only valid inside a list or map literal`, ast));
|
|
642
|
+
}
|
|
643
|
+
},
|
|
516
644
|
"comprehension": {
|
|
517
645
|
check(chk, ast, ctx) {
|
|
518
646
|
const args = ast.args;
|
|
@@ -76,8 +76,9 @@ export declare class Parser {
|
|
|
76
76
|
parsePostfix(): ASTNode;
|
|
77
77
|
parsePrimary(): ASTNode;
|
|
78
78
|
parseList(): ASTNode;
|
|
79
|
+
parseListElement(): ASTNode;
|
|
79
80
|
parseMap(): ASTNode;
|
|
80
|
-
parseProperty(): [ASTNode, ASTNode];
|
|
81
|
+
parseProperty(): [ASTNode, ASTNode] | [ASTNode];
|
|
81
82
|
parseArgumentList(): ASTNode[];
|
|
82
83
|
}
|
|
83
84
|
export {};
|
|
@@ -34,7 +34,8 @@ const TOKEN = {
|
|
|
34
34
|
COMMA: 28,
|
|
35
35
|
COLON: 29,
|
|
36
36
|
QUESTION: 30,
|
|
37
|
-
BYTES: 31
|
|
37
|
+
BYTES: 31,
|
|
38
|
+
ELLIPSIS: 32
|
|
38
39
|
};
|
|
39
40
|
const OP_FOR_TOKEN = {
|
|
40
41
|
[TOKEN.EQ]: OPS["=="],
|
|
@@ -208,6 +209,8 @@ class Lexer {
|
|
|
208
209
|
case "}":
|
|
209
210
|
return this.token(this.pos++, TOKEN.RBRACE);
|
|
210
211
|
case ".":
|
|
212
|
+
if (input[pos + 1] === "." && input[pos + 2] === ".")
|
|
213
|
+
return this.token((this.pos += 3) - 3, TOKEN.ELLIPSIS);
|
|
211
214
|
return this.token(this.pos++, TOKEN.DOT);
|
|
212
215
|
case ",":
|
|
213
216
|
return this.token(this.pos++, TOKEN.COMMA);
|
|
@@ -735,12 +738,12 @@ export class Parser {
|
|
|
735
738
|
const elements = [];
|
|
736
739
|
let remainingElements = this.limits.maxListElements;
|
|
737
740
|
if (!this.match(TOKEN.RBRACKET)) {
|
|
738
|
-
elements.push(this.
|
|
741
|
+
elements.push(this.parseListElement());
|
|
739
742
|
if (!remainingElements--) this.#limitExceeded("maxListElements", elements.at(-1).pos);
|
|
740
743
|
while (this.match(TOKEN.COMMA)) {
|
|
741
744
|
this.#advanceToken();
|
|
742
745
|
if (this.match(TOKEN.RBRACKET)) break;
|
|
743
|
-
elements.push(this.
|
|
746
|
+
elements.push(this.parseListElement());
|
|
744
747
|
if (!remainingElements--) this.#limitExceeded("maxListElements", elements.at(-1).pos);
|
|
745
748
|
}
|
|
746
749
|
}
|
|
@@ -748,6 +751,10 @@ export class Parser {
|
|
|
748
751
|
this.consume(TOKEN.RBRACKET);
|
|
749
752
|
return this.#node(start, closeEnd, OPS.list, elements);
|
|
750
753
|
}
|
|
754
|
+
parseListElement() {
|
|
755
|
+
if (this.match(TOKEN.ELLIPSIS)) return this.#parseSpread();
|
|
756
|
+
return this.parseExpression();
|
|
757
|
+
}
|
|
751
758
|
parseMap() {
|
|
752
759
|
const start = this.consume(TOKEN.LBRACE);
|
|
753
760
|
const props = [];
|
|
@@ -767,8 +774,18 @@ export class Parser {
|
|
|
767
774
|
return this.#node(start, closeEnd, OPS.map, props);
|
|
768
775
|
}
|
|
769
776
|
parseProperty() {
|
|
777
|
+
if (this.match(TOKEN.ELLIPSIS)) return [this.#parseSpread()];
|
|
770
778
|
return [this.parseExpression(), (this.consume(TOKEN.COLON), this.parseExpression())];
|
|
771
779
|
}
|
|
780
|
+
// `...expr` — only valid as a list element or map entry; the parent literal
|
|
781
|
+
// consumes the node and `parsePrimary` never sees ELLIPSIS, so a stray `...`
|
|
782
|
+
// surfaces as a normal "unexpected token" parse error.
|
|
783
|
+
#parseSpread() {
|
|
784
|
+
const start = this.pos;
|
|
785
|
+
this.#advanceToken();
|
|
786
|
+
const inner = this.parseExpression();
|
|
787
|
+
return this.#node(start, inner.end, OPS.spread, inner);
|
|
788
|
+
}
|
|
772
789
|
parseArgumentList() {
|
|
773
790
|
const args = [];
|
|
774
791
|
let remainingArgs = this.limits.maxCallArguments;
|
|
@@ -79,7 +79,11 @@ export function serialize(ast) {
|
|
|
79
79
|
case "list":
|
|
80
80
|
return `[${args.map(serialize).join(", ")}]`;
|
|
81
81
|
case "map":
|
|
82
|
-
return `{${args.map(
|
|
82
|
+
return `{${args.map(
|
|
83
|
+
(e) => e.length === 1 || e[0].op === "spread" ? serialize(e[0]) : `${serialize(e[0])}: ${serialize(e[1])}`
|
|
84
|
+
).join(", ")}}`;
|
|
85
|
+
case "spread":
|
|
86
|
+
return `...${serialize(args)}`;
|
|
83
87
|
case "?:": {
|
|
84
88
|
const ternArgs = args;
|
|
85
89
|
return `${wrap(ternArgs[0], op)} ? ${wrap(ternArgs[1], op)} : ${serialize(ternArgs[2])}`;
|