@shwfed/config 2.1.2 → 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/config/blocks/2026-05-06/com.shwfed.block.form/config.d.vue.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/config.vue.d.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/runtime.d.vue.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/runtime.vue.d.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/schema.d.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.d.vue.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.vue.d.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.d.vue.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.vue.d.ts +8 -2
- package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/schema.d.ts +12 -3
- package/dist/runtime/components/form/config.vue +36 -5
- 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/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.d.vue.ts +8 -2
- package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.vue +51 -15
- package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.vue.d.ts +8 -2
- package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/runtime.vue +16 -5
- package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/schema.d.ts +9 -1
- package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/schema.js +25 -4
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.d.vue.ts +2 -2
- package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.vue.d.ts +2 -2
- package/dist/runtime/components/form/index.vue +4 -1
- package/dist/runtime/components/form/schema.d.ts +14 -4
- package/dist/runtime/components/form/schema.js +30 -10
- package/dist/runtime/components/form/utils/initial.d.ts +26 -0
- package/dist/runtime/components/form/utils/initial.js +11 -0
- package/dist/runtime/components/table/index.vue +3 -1
- package/dist/runtime/components/table/schema.d.ts +16 -4
- 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 +23 -7
- package/dist/runtime/vendor/cel-js/lib/http-builder.d.ts +6 -3
- package/dist/runtime/vendor/cel-js/lib/http-builder.js +24 -3
- package/dist/runtime/vendor/cel-js/lib/http-builtins.d.ts +3 -3
- package/dist/runtime/vendor/cel-js/lib/http-builtins.js +0 -4
- 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/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.vue
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import { Switch } from "../../../../ui/switch";
|
|
30
30
|
import { getStructFieldDescription, getStructFieldTitle } from "../../../schema";
|
|
31
31
|
import { DEFAULT_FIELD_ORIENTATION, FIELD_ORIENTATION_OPTIONS } from "../../../utils/common";
|
|
32
|
-
import { schema } from "./schema";
|
|
32
|
+
import { JSON_VAR, schema } from "./schema";
|
|
33
33
|
defineOptions({ name: "ShwfedUploadFieldConfig" });
|
|
34
34
|
const value = defineModel({ type: null, ...{ required: true } });
|
|
35
35
|
const fieldSchema = schema(() => {
|
|
@@ -48,14 +48,30 @@ const pathText = computed({
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
|
-
const
|
|
52
|
-
get: () => value.value.template ?? "",
|
|
51
|
+
const templateRequestText = computed({
|
|
52
|
+
get: () => value.value.template?.request ?? "",
|
|
53
53
|
set: (next) => {
|
|
54
54
|
if (next.length === 0) {
|
|
55
55
|
const { template: _omit, ...rest } = value.value;
|
|
56
56
|
value.value = rest;
|
|
57
57
|
} else {
|
|
58
|
-
value.value = {
|
|
58
|
+
value.value = {
|
|
59
|
+
...value.value,
|
|
60
|
+
template: { ...value.value.template, request: next }
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const templateDownloadText = computed({
|
|
66
|
+
get: () => value.value.template?.download ?? "",
|
|
67
|
+
set: (next) => {
|
|
68
|
+
const current = value.value.template;
|
|
69
|
+
if (!current) return;
|
|
70
|
+
if (next.length === 0) {
|
|
71
|
+
const { download: _omit, ...rest } = current;
|
|
72
|
+
value.value = { ...value.value, template: rest };
|
|
73
|
+
} else {
|
|
74
|
+
value.value = { ...value.value, template: { ...current, download: next } };
|
|
59
75
|
}
|
|
60
76
|
}
|
|
61
77
|
});
|
|
@@ -482,30 +498,50 @@ function setMultiple(v) {
|
|
|
482
498
|
</Field>
|
|
483
499
|
</div>
|
|
484
500
|
|
|
485
|
-
<div class="grid grid-cols-
|
|
501
|
+
<div class="grid grid-cols-2 gap-3">
|
|
486
502
|
<Field orientation="vertical">
|
|
487
503
|
<FieldLabel class="text-xs text-zinc-500">
|
|
488
|
-
<template
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
504
|
+
<template #tooltip>
|
|
505
|
+
<Markdown
|
|
506
|
+
:source="fieldDescription('template') ?? '\u8FD4\u56DE `HttpRequest` \u7684 CEL \u8868\u8FBE\u5F0F\uFF1B\u8BBE\u7F6E\u540E\u4F1A\u6E32\u67D3\u4E0B\u8F7D\u6A21\u677F\u6309\u94AE'"
|
|
507
|
+
block
|
|
508
|
+
class="prose prose-sm prose-zinc"
|
|
509
|
+
/>
|
|
510
|
+
</template>
|
|
511
|
+
请求
|
|
512
|
+
</FieldLabel>
|
|
513
|
+
<ExpressionEditor
|
|
514
|
+
:model-value="templateRequestText"
|
|
515
|
+
placeholder="例:http.get('https://api.example.com/template.xlsx')"
|
|
516
|
+
result-type="HttpRequest"
|
|
517
|
+
class="min-h-10"
|
|
518
|
+
@update:model-value="(v) => templateRequestText = v"
|
|
519
|
+
/>
|
|
520
|
+
</Field>
|
|
521
|
+
|
|
522
|
+
<Field orientation="vertical">
|
|
523
|
+
<FieldLabel class="text-xs text-zinc-500">
|
|
524
|
+
<template #tooltip>
|
|
492
525
|
<Markdown
|
|
493
|
-
|
|
526
|
+
source="可选的第二步:用第一步响应 `json` 中的凭据构造真正的下载请求,例如 `json.data.key`"
|
|
494
527
|
block
|
|
495
528
|
class="prose prose-sm prose-zinc"
|
|
496
529
|
/>
|
|
497
530
|
</template>
|
|
498
|
-
|
|
531
|
+
下载请求
|
|
499
532
|
</FieldLabel>
|
|
500
533
|
<ExpressionEditor
|
|
501
|
-
:model-value="
|
|
502
|
-
placeholder="例:
|
|
503
|
-
result-type="
|
|
534
|
+
:model-value="templateDownloadText"
|
|
535
|
+
placeholder="例:http.get('https://api.example.com/download').query('key', json.data.key)"
|
|
536
|
+
result-type="HttpRequest"
|
|
537
|
+
:extra-vars="{ json: JSON_VAR }"
|
|
504
538
|
class="min-h-10"
|
|
505
|
-
@update:model-value="(v) =>
|
|
539
|
+
@update:model-value="(v) => templateDownloadText = v"
|
|
506
540
|
/>
|
|
507
541
|
</Field>
|
|
542
|
+
</div>
|
|
508
543
|
|
|
544
|
+
<div class="grid grid-cols-2 gap-3">
|
|
509
545
|
<Field orientation="vertical">
|
|
510
546
|
<FieldLabel class="text-xs text-zinc-500">
|
|
511
547
|
<template
|
package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.vue.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
11
11
|
readonly locale: "en" | "ja" | "ko";
|
|
12
12
|
readonly message: string;
|
|
13
13
|
}[]] | undefined;
|
|
14
|
-
readonly template?:
|
|
14
|
+
readonly template?: {
|
|
15
|
+
readonly download?: string | undefined;
|
|
16
|
+
readonly request: string;
|
|
17
|
+
} | undefined;
|
|
15
18
|
readonly disabled?: string | undefined;
|
|
16
19
|
readonly type: "com.shwfed.form.field.upload";
|
|
17
20
|
readonly id: string;
|
|
@@ -65,7 +68,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
65
68
|
readonly locale: "en" | "ja" | "ko";
|
|
66
69
|
readonly message: string;
|
|
67
70
|
}[]] | undefined;
|
|
68
|
-
readonly template?:
|
|
71
|
+
readonly template?: {
|
|
72
|
+
readonly download?: string | undefined;
|
|
73
|
+
readonly request: string;
|
|
74
|
+
} | undefined;
|
|
69
75
|
readonly disabled?: string | undefined;
|
|
70
76
|
readonly type: "com.shwfed.form.field.upload";
|
|
71
77
|
readonly id: string;
|
package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/runtime.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { Icon } from "@iconify/vue";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
|
+
import { Fetch } from "fx-fetch";
|
|
4
5
|
import { computed, ref } from "vue";
|
|
5
6
|
import { useI18n } from "vue-i18n";
|
|
6
7
|
import { toast } from "vue-sonner";
|
|
@@ -215,12 +216,22 @@ function fileIcon(file) {
|
|
|
215
216
|
}
|
|
216
217
|
const templateIcon = computed(() => props.config.templateIcon ?? "fluent:arrow-download-20-regular");
|
|
217
218
|
async function onDownloadTemplate() {
|
|
218
|
-
const
|
|
219
|
-
if (!
|
|
219
|
+
const template = props.config.template;
|
|
220
|
+
if (!template) return;
|
|
221
|
+
const ctx = { form: state.value ?? {} };
|
|
222
|
+
const program = Effect.gen(function* () {
|
|
223
|
+
const requestBuilder = yield* $cel(template.request, ctx);
|
|
224
|
+
if (!template.download) {
|
|
225
|
+
return yield* requestBuilder.file();
|
|
226
|
+
}
|
|
227
|
+
const json = yield* requestBuilder.json();
|
|
228
|
+
const downloadBuilder = yield* $cel(template.download, { ...ctx, json });
|
|
229
|
+
return yield* downloadBuilder.file();
|
|
230
|
+
});
|
|
220
231
|
try {
|
|
221
|
-
const file = await Effect.runPromise(
|
|
232
|
+
const file = await Effect.runPromise(Effect.provide(program, Fetch.layer));
|
|
222
233
|
if (!(file instanceof File)) {
|
|
223
|
-
console.error(`[shwfed-form] template
|
|
234
|
+
console.error(`[shwfed-form] template request for ${props.fieldId} did not produce a File`);
|
|
224
235
|
return;
|
|
225
236
|
}
|
|
226
237
|
const url = URL.createObjectURL(file);
|
|
@@ -232,7 +243,7 @@ async function onDownloadTemplate() {
|
|
|
232
243
|
a.remove();
|
|
233
244
|
URL.revokeObjectURL(url);
|
|
234
245
|
} catch (err) {
|
|
235
|
-
console.error(`[shwfed-form] failed to
|
|
246
|
+
console.error(`[shwfed-form] failed to download template for ${props.fieldId}:`, err);
|
|
236
247
|
}
|
|
237
248
|
}
|
|
238
249
|
</script>
|
package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/schema.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export declare const metadata: {
|
|
|
17
17
|
readonly grow: true;
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
|
+
export declare const JSON_VAR: {
|
|
21
|
+
readonly type: "dyn";
|
|
22
|
+
readonly label: "HTTP 响应体";
|
|
23
|
+
readonly description: "第一步 `请求` 的响应体(已解析 JSON);用于构造第二步下载请求";
|
|
24
|
+
};
|
|
20
25
|
export declare function schema(configure: (env: Environment) => void): Schema.Struct<{
|
|
21
26
|
label: Schema.optional<Schema.TupleType<readonly [Schema.Struct<{
|
|
22
27
|
locale: Schema.Literal<["zh"]>;
|
|
@@ -61,7 +66,10 @@ export declare function schema(configure: (env: Environment) => void): Schema.St
|
|
|
61
66
|
maxFileSize: Schema.optional<Schema.refine<number, typeof Schema.Number>>;
|
|
62
67
|
maxTotalSize: Schema.optional<Schema.refine<number, typeof Schema.Number>>;
|
|
63
68
|
maxFiles: Schema.optional<Schema.refine<number, Schema.filter<typeof Schema.Number>>>;
|
|
64
|
-
template: Schema.optional<Schema.
|
|
69
|
+
template: Schema.optional<Schema.Struct<{
|
|
70
|
+
request: Schema.Schema<string, string, never>;
|
|
71
|
+
download: Schema.optional<Schema.Schema<string, string, never>>;
|
|
72
|
+
}>>;
|
|
65
73
|
templateIcon: Schema.optional<Schema.refine<string, typeof Schema.String>>;
|
|
66
74
|
templateLabel: Schema.optional<Schema.TupleType<readonly [Schema.Struct<{
|
|
67
75
|
locale: Schema.Literal<["zh"]>;
|
package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/schema.js
CHANGED
|
@@ -10,9 +10,21 @@ export const metadata = {
|
|
|
10
10
|
w: { initial: 8, min: 8, max: Infinity },
|
|
11
11
|
h: { initial: 6, min: 6, max: Infinity, grow: true }
|
|
12
12
|
};
|
|
13
|
+
export const JSON_VAR = {
|
|
14
|
+
type: "dyn",
|
|
15
|
+
label: "HTTP \u54CD\u5E94\u4F53",
|
|
16
|
+
description: "\u7B2C\u4E00\u6B65 `\u8BF7\u6C42` \u7684\u54CD\u5E94\u4F53\uFF08\u5DF2\u89E3\u6790 JSON\uFF09\uFF1B\u7528\u4E8E\u6784\u9020\u7B2C\u4E8C\u6B65\u4E0B\u8F7D\u8BF7\u6C42"
|
|
17
|
+
};
|
|
18
|
+
function configureWithJson(configure) {
|
|
19
|
+
return (env) => {
|
|
20
|
+
configure(env);
|
|
21
|
+
env.registerVariable("json", JSON_VAR.type, { description: JSON_VAR.description });
|
|
22
|
+
};
|
|
23
|
+
}
|
|
13
24
|
export function schema(configure) {
|
|
14
25
|
const CelBool = Expression({ configure, resultType: "bool" });
|
|
15
|
-
const
|
|
26
|
+
const CelHttpRequest = Expression({ configure, resultType: "HttpRequest" });
|
|
27
|
+
const CelDownloadRequest = Expression({ configure: configureWithJson(configure), resultType: "HttpRequest" });
|
|
16
28
|
const LocaleMd = LocaleMarkdown({ configure });
|
|
17
29
|
return Schema.Struct({
|
|
18
30
|
type: Schema.Literal(type),
|
|
@@ -67,9 +79,18 @@ export function schema(configure) {
|
|
|
67
79
|
title: "\u6587\u4EF6\u6570\u91CF\u9650\u5236",
|
|
68
80
|
description: "\u5141\u8BB8\u540C\u65F6\u4E0A\u4F20\u7684\u6700\u5927\u6587\u4EF6\u6570\uFF1B\u8FBE\u5230\u4E0A\u9650\u540E\u65B0\u589E\u6587\u4EF6\u4F1A\u88AB\u62D2\u7EDD"
|
|
69
81
|
})),
|
|
70
|
-
template: Schema.optional(
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
template: Schema.optional(Schema.Struct({
|
|
83
|
+
request: CelHttpRequest.annotations({
|
|
84
|
+
title: "\u8BF7\u6C42",
|
|
85
|
+
description: "\u8FD4\u56DE `HttpRequest` \u7684 CEL \u8868\u8FBE\u5F0F\u3002\u672A\u914D\u7F6E\u300C\u4E0B\u8F7D\u8BF7\u6C42\u300D\u65F6\u76F4\u63A5\u53D1\u8D77\u5E76\u4E0B\u8F7D\u5176\u54CD\u5E94\uFF1B\u914D\u7F6E\u4E86\u300C\u4E0B\u8F7D\u8BF7\u6C42\u300D\u65F6\u4F5C\u4E3A\u83B7\u53D6\u4E0B\u8F7D\u51ED\u636E\u7684\u7B2C\u4E00\u6B65\u8BF7\u6C42"
|
|
86
|
+
}),
|
|
87
|
+
download: Schema.optional(CelDownloadRequest.annotations({
|
|
88
|
+
title: "\u4E0B\u8F7D\u8BF7\u6C42",
|
|
89
|
+
description: "\u53EF\u9009\u7684\u7B2C\u4E8C\u6B65\uFF1A\u8FD4\u56DE `HttpRequest` \u7684 CEL \u8868\u8FBE\u5F0F\uFF0C\u53EF\u901A\u8FC7 `json` \u5F15\u7528\u7B2C\u4E00\u6B65\u7684\u54CD\u5E94\u4F53\uFF0C\u4F8B\u5982\u7528 `json.data.key` \u6784\u9020\u771F\u6B63\u7684\u4E0B\u8F7D\u8BF7\u6C42"
|
|
90
|
+
}))
|
|
91
|
+
}).annotations({
|
|
92
|
+
title: "\u4E0B\u8F7D\u6A21\u677F",
|
|
93
|
+
description: "\u4E0B\u8F7D\u6A21\u677F\u7684\u8BF7\u6C42\u914D\u7F6E\uFF1B\u8BBE\u7F6E\u540E\u4F1A\u5728\u4E0A\u4F20\u533A\u57DF\u65C1\u6E32\u67D3\u4E0B\u8F7D\u6A21\u677F\u6309\u94AE"
|
|
73
94
|
})),
|
|
74
95
|
templateIcon: Schema.optional(Schema.String.pipe(Schema.minLength(1)).annotations({
|
|
75
96
|
title: "\u6A21\u677F\u6309\u94AE\u56FE\u6807",
|
|
@@ -33,12 +33,12 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
33
33
|
readonly message: string;
|
|
34
34
|
}[]] | undefined;
|
|
35
35
|
readonly readonly?: string | undefined;
|
|
36
|
+
readonly request: string;
|
|
36
37
|
readonly binding?: string | undefined;
|
|
37
38
|
readonly derived?: {
|
|
38
39
|
readonly mode: "formula" | "prefill";
|
|
39
40
|
readonly expression: string;
|
|
40
41
|
} | undefined;
|
|
41
|
-
readonly request: string;
|
|
42
42
|
readonly options: string;
|
|
43
43
|
readonly optionValue: string;
|
|
44
44
|
readonly optionLabel: readonly [{
|
|
@@ -80,12 +80,12 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
80
80
|
readonly message: string;
|
|
81
81
|
}[]] | undefined;
|
|
82
82
|
readonly readonly?: string | undefined;
|
|
83
|
+
readonly request: string;
|
|
83
84
|
readonly binding?: string | undefined;
|
|
84
85
|
readonly derived?: {
|
|
85
86
|
readonly mode: "formula" | "prefill";
|
|
86
87
|
readonly expression: string;
|
|
87
88
|
} | undefined;
|
|
88
|
-
readonly request: string;
|
|
89
89
|
readonly options: string;
|
|
90
90
|
readonly optionValue: string;
|
|
91
91
|
readonly optionLabel: readonly [{
|
|
@@ -33,12 +33,12 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
33
33
|
readonly message: string;
|
|
34
34
|
}[]] | undefined;
|
|
35
35
|
readonly readonly?: string | undefined;
|
|
36
|
+
readonly request: string;
|
|
36
37
|
readonly binding?: string | undefined;
|
|
37
38
|
readonly derived?: {
|
|
38
39
|
readonly mode: "formula" | "prefill";
|
|
39
40
|
readonly expression: string;
|
|
40
41
|
} | undefined;
|
|
41
|
-
readonly request: string;
|
|
42
42
|
readonly options: string;
|
|
43
43
|
readonly optionValue: string;
|
|
44
44
|
readonly optionLabel: readonly [{
|
|
@@ -80,12 +80,12 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
|
|
|
80
80
|
readonly message: string;
|
|
81
81
|
}[]] | undefined;
|
|
82
82
|
readonly readonly?: string | undefined;
|
|
83
|
+
readonly request: string;
|
|
83
84
|
readonly binding?: string | undefined;
|
|
84
85
|
readonly derived?: {
|
|
85
86
|
readonly mode: "formula" | "prefill";
|
|
86
87
|
readonly expression: string;
|
|
87
88
|
} | undefined;
|
|
88
|
-
readonly request: string;
|
|
89
89
|
readonly options: string;
|
|
90
90
|
readonly optionValue: string;
|
|
91
91
|
readonly optionLabel: readonly [{
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cel as _rawCel } from "../../utils/cel";
|
|
3
3
|
import { TZDate } from "@date-fns/tz";
|
|
4
4
|
import { Effect } from "effect";
|
|
5
|
+
import { Fetch } from "fx-fetch";
|
|
5
6
|
import { computed, onMounted, ref, watch } from "vue";
|
|
6
7
|
import {
|
|
7
8
|
celBindings,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
import { provideCommitBus } from "./utils/commit-bus";
|
|
12
13
|
import { provideDerivedQuiescence, useDerived } from "./utils/derived";
|
|
13
14
|
import { useFormHistory } from "./utils/history";
|
|
15
|
+
import { evaluateInitial } from "./utils/initial";
|
|
14
16
|
import { provideFormReadonly } from "./utils/readonly";
|
|
15
17
|
import { findField } from "./utils/resolve";
|
|
16
18
|
import { provideFormState } from "./utils/state";
|
|
@@ -62,7 +64,8 @@ const seeded = (() => {
|
|
|
62
64
|
const current = state.value ?? {};
|
|
63
65
|
const initial = config.value.initial;
|
|
64
66
|
if (!initial || Object.keys(current).length > 0) return Promise.resolve();
|
|
65
|
-
|
|
67
|
+
const program = evaluateInitial(initial, $cel, { form: current });
|
|
68
|
+
return Effect.runPromise(Effect.provide(program, Fetch.layer)).then(
|
|
66
69
|
(result) => {
|
|
67
70
|
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
68
71
|
state.value = { ...current, ...result };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Schema } from 'effect';
|
|
2
2
|
import type { Environment } from '../../vendor/cel-js/lib/index.js';
|
|
3
|
+
import type { InitialSource } from './utils/initial.js';
|
|
3
4
|
import { type LayoutSetValue } from '../../share/layout.js';
|
|
4
5
|
export { commonFieldFields } from './utils/common.js';
|
|
5
6
|
export { getStructFieldTitle, getStructFieldDescription } from './utils/schema-meta.js';
|
|
@@ -79,7 +80,10 @@ export declare function FormUnit(configure: (env: Environment) => void): Schema.
|
|
|
79
80
|
*/
|
|
80
81
|
export declare function registerFormVariablesIfAbsent(env: Environment): void;
|
|
81
82
|
export declare function FormConfig(configure: (env: Environment) => void): Schema.refine<{
|
|
82
|
-
readonly initial?:
|
|
83
|
+
readonly initial?: {
|
|
84
|
+
readonly data: string;
|
|
85
|
+
readonly request?: string | undefined;
|
|
86
|
+
} | undefined;
|
|
83
87
|
readonly kind: "shwfed.component.form";
|
|
84
88
|
readonly fields: readonly any[];
|
|
85
89
|
readonly layouts: readonly {
|
|
@@ -134,11 +138,17 @@ export declare function FormConfig(configure: (env: Environment) => void): Schem
|
|
|
134
138
|
}>;
|
|
135
139
|
}>>>;
|
|
136
140
|
kind: Schema.tag<"shwfed.component.form">;
|
|
137
|
-
initial: Schema.optional<Schema.
|
|
141
|
+
initial: Schema.optional<Schema.Struct<{
|
|
142
|
+
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
143
|
+
data: Schema.Schema<string, string, never>;
|
|
144
|
+
}>>;
|
|
138
145
|
readonly: Schema.optional<Schema.Schema<string, string, never>>;
|
|
139
146
|
}>>;
|
|
140
147
|
export declare function createFormConfig(body: Omit<Schema.Schema.Type<ReturnType<typeof FormConfig>>, 'kind'>): {
|
|
141
|
-
initial?:
|
|
148
|
+
initial?: {
|
|
149
|
+
readonly data: string;
|
|
150
|
+
readonly request?: string | undefined;
|
|
151
|
+
} | undefined;
|
|
142
152
|
fields: readonly any[];
|
|
143
153
|
layouts: readonly {
|
|
144
154
|
readonly name: string;
|
|
@@ -173,6 +183,6 @@ export type FormUnitValue = Readonly<{
|
|
|
173
183
|
}>;
|
|
174
184
|
export type FormConfigValue = Readonly<{
|
|
175
185
|
kind: typeof KIND;
|
|
176
|
-
initial?:
|
|
186
|
+
initial?: InitialSource;
|
|
177
187
|
readonly?: string;
|
|
178
188
|
}> & FormUnitValue;
|
|
@@ -47,23 +47,43 @@ export function FormConfig(configure) {
|
|
|
47
47
|
registerFormVariablesIfAbsent(env);
|
|
48
48
|
configure(env);
|
|
49
49
|
};
|
|
50
|
-
const
|
|
50
|
+
const CelInitialRequest = Expression({ configure: formConfigure, resultType: "HttpRequest" });
|
|
51
|
+
const CelInitialData = Expression({
|
|
52
|
+
configure: (env) => {
|
|
53
|
+
formConfigure(env);
|
|
54
|
+
env.registerVariable("json", "optional<dyn>", {
|
|
55
|
+
description: "HTTP \u54CD\u5E94\u4F53\uFF08\u5DF2\u89E3\u6790 JSON\uFF09\uFF1B\u672A\u914D\u7F6E request \u65F6\u4E3A none\uFF0C\u7528 `json.?\u5B57\u6BB5` \u8BBF\u95EE"
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
resultType: "dyn"
|
|
59
|
+
});
|
|
51
60
|
const CelFormReadonly = Expression({ configure: formConfigure, resultType: "bool" });
|
|
52
61
|
return Schema.Struct({
|
|
53
62
|
kind: Schema.tag(KIND),
|
|
54
|
-
initial: Schema.optional(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
initial: Schema.optional(Schema.Struct({
|
|
64
|
+
request: Schema.optional(CelInitialRequest.annotations({
|
|
65
|
+
title: "\u8BF7\u6C42",
|
|
66
|
+
description: md`
|
|
67
|
+
可选的 HTTP 请求表达式:返回 \`HttpRequest\`,运行时由宿主发起,并把响应体作为 \`json\` 传给「数据」表达式。
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
留空时「数据」直接对 CEL 上下文求值(静态默认值,或引用 \`form\` 的表达式)。
|
|
70
|
+
`
|
|
71
|
+
})),
|
|
72
|
+
data: CelInitialData.annotations({
|
|
73
|
+
title: "\u6570\u636E",
|
|
74
|
+
description: md`
|
|
75
|
+
返回整个表单初始值的 CEL 表达式(应为一个对象):
|
|
60
76
|
|
|
61
|
-
|
|
77
|
+
- 可以互相依赖,但应避免循环引用。考虑一个含「用户名」与「用户角色」的表单:可默认填入当前登入人的用户名,再以此为依据填入其默认角色。
|
|
62
78
|
|
|
63
|
-
|
|
79
|
+
- 配置了「请求」时,可通过 \`json\` 引用响应体,例如 \`json.?data\`。
|
|
64
80
|
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
- 如果配置了初始值,重置这个表单将**重置为其初始值,而非空**。
|
|
82
|
+
`
|
|
83
|
+
})
|
|
84
|
+
}).annotations({
|
|
85
|
+
title: "\u521D\u59CB\u503C",
|
|
86
|
+
description: "\u8868\u5355\u521D\u59CB\u503C\u7684\u6765\u6E90\uFF1A\u53EF\u9009\u7684 HTTP \u8BF7\u6C42\uFF0C\u52A0\u4E00\u4E2A\u8FD4\u56DE\u521D\u59CB\u503C\u5BF9\u8C61\u7684 CEL \u8868\u8FBE\u5F0F"
|
|
67
87
|
})),
|
|
68
88
|
readonly: Schema.optional(CelFormReadonly.annotations({
|
|
69
89
|
title: "\u53EA\u8BFB\u6761\u4EF6",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { Fetch } from 'fx-fetch';
|
|
3
|
+
/**
|
|
4
|
+
* The form's initial-state source. Mirrors the table's `dataSource` shape: an
|
|
5
|
+
* optional HTTP `request` whose response feeds the `data` expression, which
|
|
6
|
+
* produces the seed object. With no `request`, `data` is evaluated directly
|
|
7
|
+
* against the CEL context (a static default, or one referencing `form`).
|
|
8
|
+
*/
|
|
9
|
+
export type InitialSource = Readonly<{
|
|
10
|
+
request?: string;
|
|
11
|
+
data: string;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Evaluates a CEL expression against the host's context. A CEL `http.*`
|
|
15
|
+
* expression only *builds* a request, so this never performs IO itself —
|
|
16
|
+
* `evaluateInitial` issues the request separately.
|
|
17
|
+
*/
|
|
18
|
+
export type CelEvaluator = <T>(expression: string, context?: Record<string, unknown>) => Effect.Effect<T, unknown, never>;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve an `InitialSource` to the seed value. When `request` is set it is
|
|
21
|
+
* built by CEL, then issued here as `.json()` — the host owns the IO, CEL only
|
|
22
|
+
* describes it. The response is exposed to `data` as the `json` variable (an
|
|
23
|
+
* `Option`, so a missing response is `None`, never a crash). The returned
|
|
24
|
+
* Effect needs a `Fetch` layer.
|
|
25
|
+
*/
|
|
26
|
+
export declare function evaluateInitial(initial: InitialSource, cel: CelEvaluator, context: Record<string, unknown>): Effect.Effect<unknown, unknown, Fetch.Fetch>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
export function evaluateInitial(initial, cel, context) {
|
|
3
|
+
return Effect.gen(function* () {
|
|
4
|
+
let json = Option.none();
|
|
5
|
+
if (initial.request) {
|
|
6
|
+
const builder = yield* cel(initial.request, context);
|
|
7
|
+
json = Option.some(yield* builder.json());
|
|
8
|
+
}
|
|
9
|
+
return yield* cel(initial.data, { ...context, json });
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -24,6 +24,7 @@ import { NumberField, NumberFieldInput } from "../ui/number-field";
|
|
|
24
24
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
|
25
25
|
import ShwfedActions from "../actions/components/group.vue";
|
|
26
26
|
import ShwfedForm from "../form/index.vue";
|
|
27
|
+
import { evaluateInitial } from "../form/utils/initial";
|
|
27
28
|
import { provideTableInstanceId } from "./utils/instance";
|
|
28
29
|
import { provideEventTarget } from "../../share/event-bus";
|
|
29
30
|
import { findColumn } from "./utils/resolve";
|
|
@@ -294,7 +295,8 @@ async function resetQuery() {
|
|
|
294
295
|
return;
|
|
295
296
|
}
|
|
296
297
|
try {
|
|
297
|
-
const
|
|
298
|
+
const program = evaluateInitial(initial, $cel, { query: {} });
|
|
299
|
+
const result = await Effect.runPromise(Effect.provide(program, Fetch.layer));
|
|
298
300
|
queryState.value = result && typeof result === "object" && !Array.isArray(result) ? { ...result } : {};
|
|
299
301
|
} catch (e) {
|
|
300
302
|
console.error("[shwfed-table] resetQuery failed", e);
|
|
@@ -201,7 +201,10 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
|
|
|
201
201
|
}[];
|
|
202
202
|
} | undefined;
|
|
203
203
|
readonly query?: {
|
|
204
|
-
readonly initial?:
|
|
204
|
+
readonly initial?: {
|
|
205
|
+
readonly data: string;
|
|
206
|
+
readonly request?: string | undefined;
|
|
207
|
+
} | undefined;
|
|
205
208
|
readonly kind: "shwfed.component.form";
|
|
206
209
|
readonly fields: readonly any[];
|
|
207
210
|
readonly layouts: readonly {
|
|
@@ -371,7 +374,10 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
|
|
|
371
374
|
}>]>>;
|
|
372
375
|
}>>;
|
|
373
376
|
query: Schema.optional<Schema.refine<{
|
|
374
|
-
readonly initial?:
|
|
377
|
+
readonly initial?: {
|
|
378
|
+
readonly data: string;
|
|
379
|
+
readonly request?: string | undefined;
|
|
380
|
+
} | undefined;
|
|
375
381
|
readonly kind: "shwfed.component.form";
|
|
376
382
|
readonly fields: readonly any[];
|
|
377
383
|
readonly layouts: readonly {
|
|
@@ -426,7 +432,10 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
|
|
|
426
432
|
}>;
|
|
427
433
|
}>>>;
|
|
428
434
|
kind: Schema.tag<"shwfed.component.form">;
|
|
429
|
-
initial: Schema.optional<Schema.
|
|
435
|
+
initial: Schema.optional<Schema.Struct<{
|
|
436
|
+
request: Schema.optional<Schema.Schema<string, string, never>>;
|
|
437
|
+
data: Schema.Schema<string, string, never>;
|
|
438
|
+
}>>;
|
|
430
439
|
readonly: Schema.optional<Schema.Schema<string, string, never>>;
|
|
431
440
|
}>>>;
|
|
432
441
|
cellStyle: Schema.optional<Schema.Schema<string, string, never>>;
|
|
@@ -573,7 +582,10 @@ export declare function createTableConfig(body: Omit<Schema.Schema.Type<ReturnTy
|
|
|
573
582
|
}[];
|
|
574
583
|
} | undefined;
|
|
575
584
|
query?: {
|
|
576
|
-
readonly initial?:
|
|
585
|
+
readonly initial?: {
|
|
586
|
+
readonly data: string;
|
|
587
|
+
readonly request?: string | undefined;
|
|
588
|
+
} | undefined;
|
|
577
589
|
readonly kind: "shwfed.component.form";
|
|
578
590
|
readonly fields: readonly any[];
|
|
579
591
|
readonly layouts: readonly {
|
|
@@ -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 builds an `HttpRequestBuilder
|
|
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
|
|
@@ -197,7 +217,7 @@ location.param("id") // first value when a param repeats
|
|
|
197
217
|
|
|
198
218
|
## HTTP
|
|
199
219
|
|
|
200
|
-
Build HTTP requests using the `http` builder. A CEL expression *describes* a request
|
|
220
|
+
Build HTTP requests using the `http` builder. A CEL expression only *describes* a request — it always evaluates to an `HttpRequest`, never issues it. The host decides when and how to send it (downloading a file, reading a JSON response, etc.). There is no way to perform the request or read its response from CEL.
|
|
201
221
|
|
|
202
222
|
**URLs must be absolute** — pass a full `https://…` URL. There is no base URL, so relative paths like `/api/users` will not resolve.
|
|
203
223
|
|
|
@@ -236,12 +256,8 @@ http.post("https://api.example.com/users")
|
|
|
236
256
|
.header("Content-Type", "application/json")
|
|
237
257
|
.body({"name": "Alice", "channel": query.?channel})
|
|
238
258
|
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
// to a File (filename from the Content-Disposition header, else the URL path):
|
|
242
|
-
http.get("https://api.example.com/files/report.pdf").file()
|
|
243
|
-
|
|
244
|
-
// Full example — the expression returns an HttpRequest; the host sends it:
|
|
259
|
+
// The expression always evaluates to an HttpRequest — the host sends it
|
|
260
|
+
// (downloading a File, reading a JSON response, etc.):
|
|
245
261
|
http.post("https://api.example.com/users")
|
|
246
262
|
.header("Content-Type", "application/json")
|
|
247
263
|
.header("Authorization", "Bearer " + token)
|