@shwfed/config 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/config.d.vue.ts +8 -2
  3. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/config.vue.d.ts +8 -2
  4. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/runtime.d.vue.ts +8 -2
  5. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/runtime.vue.d.ts +8 -2
  6. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.form/schema.d.ts +8 -2
  7. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.d.vue.ts +8 -2
  8. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.vue.d.ts +8 -2
  9. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.d.vue.ts +8 -2
  10. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.vue.d.ts +8 -2
  11. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/schema.d.ts +12 -3
  12. package/dist/runtime/components/form/config.vue +36 -5
  13. package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.d.vue.ts +8 -2
  14. package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.vue +51 -15
  15. package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/config.vue.d.ts +8 -2
  16. package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/runtime.vue +16 -5
  17. package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/schema.d.ts +9 -1
  18. package/dist/runtime/components/form/fields/2026-05-12/com.shwfed.form.field.upload/schema.js +25 -4
  19. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.d.vue.ts +2 -2
  20. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.combobox.single.remote/config.vue.d.ts +2 -2
  21. package/dist/runtime/components/form/index.vue +4 -1
  22. package/dist/runtime/components/form/schema.d.ts +14 -4
  23. package/dist/runtime/components/form/schema.js +30 -10
  24. package/dist/runtime/components/form/utils/initial.d.ts +26 -0
  25. package/dist/runtime/components/form/utils/initial.js +11 -0
  26. package/dist/runtime/components/table/index.vue +3 -1
  27. package/dist/runtime/components/table/schema.d.ts +16 -4
  28. package/dist/runtime/vendor/cel-js/CLAUDE.md +1 -1
  29. package/dist/runtime/vendor/cel-js/PROMPT.md +3 -7
  30. package/dist/runtime/vendor/cel-js/lib/http-builder.d.ts +4 -3
  31. package/dist/runtime/vendor/cel-js/lib/http-builtins.d.ts +3 -3
  32. package/dist/runtime/vendor/cel-js/lib/http-builtins.js +0 -4
  33. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shwfed",
3
3
  "configKey": "shwfed",
4
- "version": "2.1.2",
4
+ "version": "2.2.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -10,7 +10,10 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
11
  "update:modelValue": (value: {
12
12
  readonly form: {
13
- readonly initial?: string | undefined;
13
+ readonly initial?: {
14
+ readonly data: string;
15
+ readonly request?: string | undefined;
16
+ } | undefined;
14
17
  readonly kind: "shwfed.component.form";
15
18
  readonly fields: readonly any[];
16
19
  readonly layouts: readonly {
@@ -39,7 +42,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
39
42
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
40
43
  "onUpdate:modelValue"?: ((value: {
41
44
  readonly form: {
42
- readonly initial?: string | undefined;
45
+ readonly initial?: {
46
+ readonly data: string;
47
+ readonly request?: string | undefined;
48
+ } | undefined;
43
49
  readonly kind: "shwfed.component.form";
44
50
  readonly fields: readonly any[];
45
51
  readonly layouts: readonly {
@@ -10,7 +10,10 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
10
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
11
  "update:modelValue": (value: {
12
12
  readonly form: {
13
- readonly initial?: string | undefined;
13
+ readonly initial?: {
14
+ readonly data: string;
15
+ readonly request?: string | undefined;
16
+ } | undefined;
14
17
  readonly kind: "shwfed.component.form";
15
18
  readonly fields: readonly any[];
16
19
  readonly layouts: readonly {
@@ -39,7 +42,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
39
42
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
40
43
  "onUpdate:modelValue"?: ((value: {
41
44
  readonly form: {
42
- readonly initial?: string | undefined;
45
+ readonly initial?: {
46
+ readonly data: string;
47
+ readonly request?: string | undefined;
48
+ } | undefined;
43
49
  readonly kind: "shwfed.component.form";
44
50
  readonly fields: readonly any[];
45
51
  readonly layouts: readonly {
@@ -12,7 +12,10 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
12
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
13
  "update:block": (value: {
14
14
  readonly form: {
15
- readonly initial?: string | undefined;
15
+ readonly initial?: {
16
+ readonly data: string;
17
+ readonly request?: string | undefined;
18
+ } | undefined;
16
19
  readonly kind: "shwfed.component.form";
17
20
  readonly fields: readonly any[];
18
21
  readonly layouts: readonly {
@@ -42,7 +45,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
42
45
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
43
46
  "onUpdate:block"?: ((value: {
44
47
  readonly form: {
45
- readonly initial?: string | undefined;
48
+ readonly initial?: {
49
+ readonly data: string;
50
+ readonly request?: string | undefined;
51
+ } | undefined;
46
52
  readonly kind: "shwfed.component.form";
47
53
  readonly fields: readonly any[];
48
54
  readonly layouts: readonly {
@@ -12,7 +12,10 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
12
12
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
13
  "update:block": (value: {
14
14
  readonly form: {
15
- readonly initial?: string | undefined;
15
+ readonly initial?: {
16
+ readonly data: string;
17
+ readonly request?: string | undefined;
18
+ } | undefined;
16
19
  readonly kind: "shwfed.component.form";
17
20
  readonly fields: readonly any[];
18
21
  readonly layouts: readonly {
@@ -42,7 +45,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
42
45
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
43
46
  "onUpdate:block"?: ((value: {
44
47
  readonly form: {
45
- readonly initial?: string | undefined;
48
+ readonly initial?: {
49
+ readonly data: string;
50
+ readonly request?: string | undefined;
51
+ } | undefined;
46
52
  readonly kind: "shwfed.component.form";
47
53
  readonly fields: readonly any[];
48
54
  readonly layouts: readonly {
@@ -22,7 +22,10 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
22
22
  type: Schema.tag<"com.shwfed.block.form">;
23
23
  compatibilityDate: Schema.tag<"2026-05-06">;
24
24
  form: Schema.refine<{
25
- readonly initial?: string | undefined;
25
+ readonly initial?: {
26
+ readonly data: string;
27
+ readonly request?: string | undefined;
28
+ } | undefined;
26
29
  readonly kind: "shwfed.component.form";
27
30
  readonly fields: readonly any[];
28
31
  readonly layouts: readonly {
@@ -77,7 +80,10 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
77
80
  }>;
78
81
  }>>>;
79
82
  kind: Schema.tag<"shwfed.component.form">;
80
- initial: Schema.optional<Schema.Schema<string, string, never>>;
83
+ initial: Schema.optional<Schema.Struct<{
84
+ request: Schema.optional<Schema.Schema<string, string, never>>;
85
+ data: Schema.Schema<string, string, never>;
86
+ }>>;
81
87
  readonly: Schema.optional<Schema.Schema<string, string, never>>;
82
88
  }>>;
83
89
  }>;
@@ -106,7 +106,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
106
106
  }[];
107
107
  } | undefined;
108
108
  readonly query?: {
109
- readonly initial?: string | undefined;
109
+ readonly initial?: {
110
+ readonly data: string;
111
+ readonly request?: string | undefined;
112
+ } | undefined;
110
113
  readonly kind: "shwfed.component.form";
111
114
  readonly fields: readonly any[];
112
115
  readonly layouts: readonly {
@@ -278,7 +281,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
278
281
  }[];
279
282
  } | undefined;
280
283
  readonly query?: {
281
- readonly initial?: string | undefined;
284
+ readonly initial?: {
285
+ readonly data: string;
286
+ readonly request?: string | undefined;
287
+ } | undefined;
282
288
  readonly kind: "shwfed.component.form";
283
289
  readonly fields: readonly any[];
284
290
  readonly layouts: readonly {
@@ -106,7 +106,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
106
106
  }[];
107
107
  } | undefined;
108
108
  readonly query?: {
109
- readonly initial?: string | undefined;
109
+ readonly initial?: {
110
+ readonly data: string;
111
+ readonly request?: string | undefined;
112
+ } | undefined;
110
113
  readonly kind: "shwfed.component.form";
111
114
  readonly fields: readonly any[];
112
115
  readonly layouts: readonly {
@@ -278,7 +281,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
278
281
  }[];
279
282
  } | undefined;
280
283
  readonly query?: {
281
- readonly initial?: string | undefined;
284
+ readonly initial?: {
285
+ readonly data: string;
286
+ readonly request?: string | undefined;
287
+ } | undefined;
282
288
  readonly kind: "shwfed.component.form";
283
289
  readonly fields: readonly any[];
284
290
  readonly layouts: readonly {
@@ -101,7 +101,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
101
101
  }[];
102
102
  } | undefined;
103
103
  readonly query?: {
104
- readonly initial?: string | undefined;
104
+ readonly initial?: {
105
+ readonly data: string;
106
+ readonly request?: string | undefined;
107
+ } | undefined;
105
108
  readonly kind: "shwfed.component.form";
106
109
  readonly fields: readonly any[];
107
110
  readonly layouts: readonly {
@@ -273,7 +276,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
273
276
  }[];
274
277
  } | undefined;
275
278
  readonly query?: {
276
- readonly initial?: string | undefined;
279
+ readonly initial?: {
280
+ readonly data: string;
281
+ readonly request?: string | undefined;
282
+ } | undefined;
277
283
  readonly kind: "shwfed.component.form";
278
284
  readonly fields: readonly any[];
279
285
  readonly layouts: readonly {
@@ -101,7 +101,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
101
101
  }[];
102
102
  } | undefined;
103
103
  readonly query?: {
104
- readonly initial?: string | undefined;
104
+ readonly initial?: {
105
+ readonly data: string;
106
+ readonly request?: string | undefined;
107
+ } | undefined;
105
108
  readonly kind: "shwfed.component.form";
106
109
  readonly fields: readonly any[];
107
110
  readonly layouts: readonly {
@@ -273,7 +276,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
273
276
  }[];
274
277
  } | undefined;
275
278
  readonly query?: {
276
- readonly initial?: string | undefined;
279
+ readonly initial?: {
280
+ readonly data: string;
281
+ readonly request?: string | undefined;
282
+ } | undefined;
277
283
  readonly kind: "shwfed.component.form";
278
284
  readonly fields: readonly any[];
279
285
  readonly layouts: readonly {
@@ -116,7 +116,10 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
116
116
  }[];
117
117
  } | undefined;
118
118
  readonly query?: {
119
- readonly initial?: string | undefined;
119
+ readonly initial?: {
120
+ readonly data: string;
121
+ readonly request?: string | undefined;
122
+ } | undefined;
120
123
  readonly kind: "shwfed.component.form";
121
124
  readonly fields: readonly any[];
122
125
  readonly layouts: readonly {
@@ -286,7 +289,10 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
286
289
  }>]>>;
287
290
  }>>;
288
291
  query: Schema.optional<Schema.refine<{
289
- readonly initial?: string | undefined;
292
+ readonly initial?: {
293
+ readonly data: string;
294
+ readonly request?: string | undefined;
295
+ } | undefined;
290
296
  readonly kind: "shwfed.component.form";
291
297
  readonly fields: readonly any[];
292
298
  readonly layouts: readonly {
@@ -341,7 +347,10 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
341
347
  }>;
342
348
  }>>>;
343
349
  kind: Schema.tag<"shwfed.component.form">;
344
- initial: Schema.optional<Schema.Schema<string, string, never>>;
350
+ initial: Schema.optional<Schema.Struct<{
351
+ request: Schema.optional<Schema.Schema<string, string, never>>;
352
+ data: Schema.Schema<string, string, never>;
353
+ }>>;
345
354
  readonly: Schema.optional<Schema.Schema<string, string, never>>;
346
355
  }>>>;
347
356
  cellStyle: Schema.optional<Schema.Schema<string, string, never>>;
@@ -32,13 +32,23 @@ const EXTRAS = [
32
32
  { id: "general", label: "\u901A\u7528\u914D\u7F6E", icon: "fluent:settings-20-regular" }
33
33
  ];
34
34
  const selection = ref({ kind: "extras", id: "general" });
35
- function updateInitial(value) {
35
+ function updateInitialData(value) {
36
36
  const next = { ...config.value };
37
37
  if (value.trim() === "") {
38
38
  const { initial: _omit, ...rest } = next;
39
39
  config.value = rest;
40
40
  } else {
41
- config.value = { ...next, initial: value };
41
+ config.value = { ...next, initial: { ...next.initial, data: value } };
42
+ }
43
+ }
44
+ function updateInitialRequest(value) {
45
+ const current = config.value.initial;
46
+ if (!current) return;
47
+ if (value.trim() === "") {
48
+ const { request: _omit, ...rest } = current;
49
+ config.value = { ...config.value, initial: rest };
50
+ } else {
51
+ config.value = { ...config.value, initial: { ...current, request: value } };
42
52
  }
43
53
  }
44
54
  function updateReadonly(value) {
@@ -73,15 +83,36 @@ function updateReadonly(value) {
73
83
  class="prose prose-sm prose-zinc"
74
84
  />
75
85
  </template>
76
- {{ generalFieldTitle("initial") }}
86
+ {{ generalFieldTitle("initial") }}(数据)
77
87
  </FieldLabel>
78
88
  <ExpressionEditor
79
- :model-value="config.initial ?? ''"
89
+ :model-value="config.initial?.data ?? ''"
80
90
  placeholder="如 { 'name': '', 'age': 0 }"
81
91
  result-type="dyn"
92
+ :extra-vars="{ json: { type: 'optional<dyn>', label: 'HTTP \u54CD\u5E94\u4F53', description: '\u300C\u8BF7\u6C42\u300D\u7684\u54CD\u5E94\u4F53\uFF08\u5DF2\u89E3\u6790 JSON\uFF09\uFF1B\u672A\u914D\u7F6E\u8BF7\u6C42\u65F6\u4E3A none' } }"
82
93
  multiline
83
94
  class="min-h-20"
84
- @update:model-value="updateInitial"
95
+ @update:model-value="updateInitialData"
96
+ />
97
+ </Field>
98
+
99
+ <Field orientation="vertical">
100
+ <FieldLabel class="text-xs text-zinc-500">
101
+ <template #tooltip>
102
+ <Markdown
103
+ source="可选的 HTTP 请求:返回 `HttpRequest`,由宿主发起,响应体作为 `json` 传给「数据」表达式"
104
+ block
105
+ class="prose prose-sm prose-zinc"
106
+ />
107
+ </template>
108
+ {{ generalFieldTitle("initial") }}(请求)
109
+ </FieldLabel>
110
+ <ExpressionEditor
111
+ :model-value="config.initial?.request ?? ''"
112
+ placeholder="如 http.get('https://api.example.com/record/1')"
113
+ result-type="HttpRequest"
114
+ class="min-h-10"
115
+ @update:model-value="updateInitialRequest"
85
116
  />
86
117
  </Field>
87
118
 
@@ -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?: string | undefined;
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?: string | undefined;
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;
@@ -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 templateText = computed({
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 = { ...value.value, template: next };
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-3 gap-3">
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
- v-if="fieldDescription('template')"
490
- #tooltip
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
- :source="fieldDescription('template')"
526
+ source="可选的第二步:用第一步响应 `json` 中的凭据构造真正的下载请求,例如 `json.data.key`"
494
527
  block
495
528
  class="prose prose-sm prose-zinc"
496
529
  />
497
530
  </template>
498
- {{ fieldTitle("template") }}
531
+ 下载请求
499
532
  </FieldLabel>
500
533
  <ExpressionEditor
501
- :model-value="templateText"
502
- placeholder="例:fetchFile('/api/template.xlsx')"
503
- result-type="File"
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) => templateText = 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
@@ -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?: string | undefined;
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?: string | undefined;
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;
@@ -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 expression = props.config.template;
219
- if (!expression) return;
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($cel(expression, { form: state.value ?? {} }));
232
+ const file = await Effect.runPromise(Effect.provide(program, Fetch.layer));
222
233
  if (!(file instanceof File)) {
223
- console.error(`[shwfed-form] template expression for ${props.fieldId} did not return a File`);
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 evaluate template for ${props.fieldId}:`, err);
246
+ console.error(`[shwfed-form] failed to download template for ${props.fieldId}:`, err);
236
247
  }
237
248
  }
238
249
  </script>
@@ -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.Schema<string, string, never>>;
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"]>;
@@ -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 CelFile = Expression({ configure, resultType: "File" });
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(CelFile.annotations({
71
- title: "\u6A21\u677F\u8868\u8FBE\u5F0F",
72
- description: "\u8FD4\u56DE `File` \u7684 CEL \u8868\u8FBE\u5F0F\uFF1B\u8BBE\u7F6E\u540E\u4F1A\u5728\u4E0A\u4F20\u533A\u57DF\u65C1\u6E32\u67D3\u4E0B\u8F7D\u6A21\u677F\u6309\u94AE"
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
- return Effect.runPromise($cel(initial, { form: current })).then(
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?: string | undefined;
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.Schema<string, string, never>>;
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?: string | undefined;
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?: string;
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 CelFormInitial = Expression({ configure: formConfigure, resultType: "dyn" });
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(CelFormInitial.annotations({
55
- title: "\u521D\u59CB\u503C",
56
- description: md`
57
- 返回整个表单初始值的 CEL 表达式:
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
- - 支持异步调用(如 \`http.get(...).json()\`)。运行时会等待表达式 resolve 后再触发依赖 \`form\` 的请求
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 result = await Effect.runPromise($cel(initial, { query: {} }));
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?: string | undefined;
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?: string | undefined;
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.Schema<string, string, never>>;
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?: string | undefined;
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 {
@@ -20,7 +20,7 @@ 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`; the terminal `HttpRequest.file(): File` method issues the request and evaluates to the downloaded `File`, while `.json()` is dispatched by the host on the builder value (it is not a CEL method). 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`.
24
24
 
25
25
  ## Architecture
26
26
 
@@ -197,7 +197,7 @@ location.param("id") // first value when a param repeats
197
197
 
198
198
  ## HTTP
199
199
 
200
- Build HTTP requests using the `http` builder. A CEL expression *describes* a request; calling `.file()` on it issues the request and evaluates to the downloaded `File`. Any other request is handed to the host to dispatch — there is no way to read a JSON or text response from CEL.
200
+ 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
201
 
202
202
  **URLs must be absolute** — pass a full `https://…` URL. There is no base URL, so relative paths like `/api/users` will not resolve.
203
203
 
@@ -236,12 +236,8 @@ http.post("https://api.example.com/users")
236
236
  .header("Content-Type", "application/json")
237
237
  .body({"name": "Alice", "channel": query.?channel})
238
238
 
239
- // Download the response as a File. Unlike the chainable methods above,
240
- // .file() is terminal it issues the request and the expression evaluates
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:
239
+ // The expression always evaluates to an HttpRequest the host sends it
240
+ // (downloading a File, reading a JSON response, etc.):
245
241
  http.post("https://api.example.com/users")
246
242
  .header("Content-Type", "application/json")
247
243
  .header("Authorization", "Bearer " + token)
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * `HttpRequestBuilder` — the value an `http.*` CEL expression evaluates to.
3
3
  *
4
- * A local (non-upstream) extension. A CEL expression builds and *describes* a
5
- * request; `.file()` is exposed as a CEL method and evaluates to the downloaded
6
- * `File`, while `.json()` is dispatched by the host on the returned builder.
4
+ * A local (non-upstream) extension. A CEL expression only *describes* a
5
+ * request it never issues one. Both terminal methods, `.json()` and
6
+ * `.file()`, are dispatched by the host on the returned builder, keeping
7
+ * expression evaluation free of IO.
7
8
  *
8
9
  * Endpoints are used verbatim — there is no base-URL resolution, so callers
9
10
  * must pass absolute URLs.
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Registers the `http` / `HttpRequest` types, the `http` constant, and the
5
5
  * request-builder functions on the registry. `http.get(url).header(...).body(...)`
6
- * builds an `HttpRequestBuilder`; the terminal `.file()` method issues the
7
- * request and evaluates to the downloaded `File`. `.json()` is *not* a CEL
8
- * method the host dispatches that on the returned builder value.
6
+ * builds an `HttpRequestBuilder` a pure *description* of a request. CEL never
7
+ * issues it: both `.json()` and `.file()` are dispatched by the host on the
8
+ * returned builder, so expression evaluation stays free of IO.
9
9
  *
10
10
  * Wired into `globalRegistry` at module load (see `evaluator.ts`), so every
11
11
  * `Environment` gets HTTP for free — both type-checking and evaluation.
@@ -52,8 +52,4 @@ export function registerHttpBuiltins(registry) {
52
52
  "HttpRequest.body(dyn): HttpRequest",
53
53
  (b, data) => b.body(data)
54
54
  );
55
- registry.registerFunctionOverload(
56
- "HttpRequest.file(): File",
57
- (b) => b.file()
58
- );
59
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/config",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {