@shwfed/config 2.0.3 → 2.1.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.
- package/dist/module.json +1 -1
- package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/config.d.vue.ts +16 -6
- package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/config.vue +263 -76
- package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/config.vue.d.ts +16 -6
- package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/runtime.d.vue.ts +16 -6
- package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/runtime.vue +104 -12
- package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/runtime.vue.d.ts +16 -6
- package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/schema.d.ts +51 -15
- package/dist/runtime/components/config/blocks/2026-05-17/{com.shwfed.block.chart.line → com.shwfed.block.chart.xy}/schema.js +67 -22
- package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/config.d.vue.ts +131 -0
- package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/config.vue +170 -0
- package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/config.vue.d.ts +131 -0
- package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/runtime.d.vue.ts +8 -0
- package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/runtime.vue +52 -0
- package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/runtime.vue.d.ts +8 -0
- package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/schema.d.ts +112 -0
- package/dist/runtime/components/form/fields/2026-05-18/com.shwfed.form.field.table/schema.js +44 -0
- package/dist/runtime/components/form/schema.d.ts +10 -0
- package/dist/runtime/components/form/schema.js +6 -2
- package/dist/runtime/components/table/config.d.vue.ts +11 -1
- package/dist/runtime/components/table/config.vue +4 -0
- package/dist/runtime/components/table/config.vue.d.ts +11 -1
- package/dist/runtime/components/table/index.d.vue.ts +4 -0
- package/dist/runtime/components/table/index.vue +28 -2
- package/dist/runtime/components/table/index.vue.d.ts +4 -0
- package/dist/runtime/components/table/schema.d.ts +12 -0
- package/dist/runtime/components/table/schema.js +6 -1
- package/dist/runtime/vendor/cel-js/PROMPT.md +6 -2
- package/dist/runtime/vendor/cel-js/lib/http-builder.d.ts +7 -1
- package/dist/runtime/vendor/cel-js/lib/http-builder.js +28 -5
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Locale } from "../../../../../share/locale.js";
|
|
3
|
+
import { TableConfig, defaultTableConfig } from "../../../../table/schema.js";
|
|
4
|
+
import { commonFieldFields } from "../../../utils/common.js";
|
|
5
|
+
export const type = "com.shwfed.form.field.table";
|
|
6
|
+
export const compatibilityDate = "2026-05-18";
|
|
7
|
+
export const metadata = {
|
|
8
|
+
name: "\u8868\u683C",
|
|
9
|
+
icon: "fluent:table-20-regular",
|
|
10
|
+
w: { initial: 12, min: 6, max: Infinity },
|
|
11
|
+
h: { initial: 10, min: 5, max: Infinity, grow: true }
|
|
12
|
+
};
|
|
13
|
+
export function schema(configure) {
|
|
14
|
+
const Table = Schema.suspend(
|
|
15
|
+
() => TableConfig(configure)
|
|
16
|
+
);
|
|
17
|
+
return Schema.Struct({
|
|
18
|
+
type: Schema.Literal(type),
|
|
19
|
+
compatibilityDate: Schema.Literal(compatibilityDate),
|
|
20
|
+
...commonFieldFields(configure),
|
|
21
|
+
label: Schema.optional(Locale.annotations({
|
|
22
|
+
title: "\u6807\u7B7E",
|
|
23
|
+
description: "\u8868\u683C\u4E0A\u65B9\u5C55\u793A\u7684\u6587\u672C\uFF1B\u7559\u7A7A\u5219\u4E0D\u6E32\u67D3\u6807\u7B7E"
|
|
24
|
+
})),
|
|
25
|
+
tooltip: Schema.optional(Locale.annotations({
|
|
26
|
+
title: "\u63D0\u793A",
|
|
27
|
+
description: "\u9F20\u6807\u60AC\u505C\u5728\u6807\u7B7E\u4E0A\u65F6\u5C55\u793A\u7684\u8BF4\u660E\uFF0C\u652F\u6301 Markdown"
|
|
28
|
+
})),
|
|
29
|
+
binding: Schema.optional(Schema.String.pipe(Schema.minLength(1)).annotations({
|
|
30
|
+
title: "\u7ED1\u5B9A\u8DEF\u5F84",
|
|
31
|
+
description: "\u8868\u683C\u5F53\u524D\u6570\u636E\u5B9E\u65F6\u5199\u5165\u8868\u5355\u72B6\u6001\u7684 `dot-prop` \u8DEF\u5F84\uFF0C\u4F8B\u5982 `rows`\uFF1B\u7559\u7A7A\u5219\u4E0D\u5199\u5165\u8868\u5355\u72B6\u6001"
|
|
32
|
+
})),
|
|
33
|
+
table: Table.annotations({
|
|
34
|
+
title: "\u8868\u683C\u914D\u7F6E",
|
|
35
|
+
description: "\u5185\u5D4C\u8868\u683C\u7684\u5B8C\u6574\u914D\u7F6E\uFF1B\u6570\u636E\u7531\u8868\u683C\u81EA\u5DF1\u7684\u6570\u636E\u6E90\u52A0\u8F7D\uFF0C\u7528\u6237\u7684\u672C\u5730\u589E\u5220\u6539\u5B9E\u65F6\u56DE\u5199\u5230\u300C\u7ED1\u5B9A\u8DEF\u5F84\u300D"
|
|
36
|
+
})
|
|
37
|
+
}).annotations({
|
|
38
|
+
title: "TableField",
|
|
39
|
+
description: "\u628A\u4E00\u4E2A\u53EF\u672C\u5730\u7F16\u8F91\u7684\u8868\u683C\u5D4C\u5165\u8868\u5355\uFF0C\u5176\u5B9E\u65F6\u6570\u636E\u5199\u5165\u7ED1\u5B9A\u8DEF\u5F84"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function defaults() {
|
|
43
|
+
return { table: defaultTableConfig() };
|
|
44
|
+
}
|
|
@@ -68,6 +68,16 @@ export declare function FormUnit(configure: (env: Environment) => void): Schema.
|
|
|
68
68
|
}>;
|
|
69
69
|
}>>>;
|
|
70
70
|
}>>;
|
|
71
|
+
/**
|
|
72
|
+
* Register the form-wide `now` / `form` live variables on `env`, skipping any
|
|
73
|
+
* already declared. Idempotent so it composes safely from either side: a
|
|
74
|
+
* `FormConfig` nested inside another (the `table` form field embeds a table
|
|
75
|
+
* whose `query` is its own sub-form, and the embedding `configure` advertises
|
|
76
|
+
* the same pair) would otherwise register `form` twice on one env and the CEL
|
|
77
|
+
* registry throws `'form' is already registered`. Mirrors
|
|
78
|
+
* `registerRowVariablesIfAbsent`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function registerFormVariablesIfAbsent(env: Environment): void;
|
|
71
81
|
export declare function FormConfig(configure: (env: Environment) => void): Schema.refine<{
|
|
72
82
|
readonly initial?: string | undefined;
|
|
73
83
|
readonly kind: "shwfed.component.form";
|
|
@@ -37,10 +37,14 @@ export function FormUnit(configure) {
|
|
|
37
37
|
description: "\u4E00\u7EC4\u8868\u5355\u5B57\u6BB5\u53CA\u5176\u5E03\u5C40"
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
+
export function registerFormVariablesIfAbsent(env) {
|
|
41
|
+
const declared = new Set(env.getDefinitions().variables.map((v) => v.name));
|
|
42
|
+
if (!declared.has("now")) env.registerVariable("now", "Date", { description: "\u5F53\u524D\u65E5\u671F/\u65F6\u95F4" });
|
|
43
|
+
if (!declared.has("form")) env.registerVariable("form", "dyn", { description: "\u5F53\u524D\u8868\u5355\u72B6\u6001" });
|
|
44
|
+
}
|
|
40
45
|
export function FormConfig(configure) {
|
|
41
46
|
const formConfigure = (env) => {
|
|
42
|
-
env
|
|
43
|
-
env.registerVariable("form", "dyn", { description: "\u5F53\u524D\u8868\u5355\u72B6\u6001" });
|
|
47
|
+
registerFormVariablesIfAbsent(env);
|
|
44
48
|
configure(env);
|
|
45
49
|
};
|
|
46
50
|
const CelFormInitial = Expression({ configure: formConfigure, resultType: "dyn" });
|
|
@@ -7,10 +7,20 @@ type __VLS_ModelProps = {
|
|
|
7
7
|
modelValue: AnyRecord;
|
|
8
8
|
};
|
|
9
9
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
10
|
-
declare
|
|
10
|
+
declare var __VLS_201: {};
|
|
11
|
+
type __VLS_Slots = {} & {
|
|
12
|
+
'general-extra'?: (props: typeof __VLS_201) => any;
|
|
13
|
+
};
|
|
14
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
15
|
"update:modelValue": (value: AnyRecord) => any;
|
|
12
16
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
13
17
|
"onUpdate:modelValue"?: ((value: AnyRecord) => any) | undefined;
|
|
14
18
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
19
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
15
20
|
declare const _default: typeof __VLS_export;
|
|
16
21
|
export default _default;
|
|
22
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
23
|
+
new (): {
|
|
24
|
+
$slots: S;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -1251,6 +1251,10 @@ const tableQueryValue = computed({
|
|
|
1251
1251
|
</InputGroup>
|
|
1252
1252
|
</Field>
|
|
1253
1253
|
|
|
1254
|
+
<!-- Host extension slot: a wrapping editor (e.g. the `table` form
|
|
1255
|
+
field config) injects its own fields into the general pane. -->
|
|
1256
|
+
<slot name="general-extra" />
|
|
1257
|
+
|
|
1254
1258
|
<div class="flex items-center gap-2">
|
|
1255
1259
|
<h3 class="text-xs font-medium text-zinc-500">
|
|
1256
1260
|
{{ generalFieldTitle("dataSource") }}
|
|
@@ -7,10 +7,20 @@ type __VLS_ModelProps = {
|
|
|
7
7
|
modelValue: AnyRecord;
|
|
8
8
|
};
|
|
9
9
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
10
|
-
declare
|
|
10
|
+
declare var __VLS_201: {};
|
|
11
|
+
type __VLS_Slots = {} & {
|
|
12
|
+
'general-extra'?: (props: typeof __VLS_201) => any;
|
|
13
|
+
};
|
|
14
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
15
|
"update:modelValue": (value: AnyRecord) => any;
|
|
12
16
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
13
17
|
"onUpdate:modelValue"?: ((value: AnyRecord) => any) | undefined;
|
|
14
18
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
19
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
15
20
|
declare const _default: typeof __VLS_export;
|
|
16
21
|
export default _default;
|
|
22
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
23
|
+
new (): {
|
|
24
|
+
$slots: S;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -12,6 +12,7 @@ declare const _default: typeof __VLS_export;
|
|
|
12
12
|
export default _default;
|
|
13
13
|
declare const __VLS_export: import("vue").DefineComponent<{
|
|
14
14
|
config?: TableConfigValue;
|
|
15
|
+
rows?: Array<unknown>;
|
|
15
16
|
}, import("@tanstack/vue-table").Table<unknown>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
17
|
"update:config": (value: Readonly<{
|
|
17
18
|
kind: "shwfed.component.table";
|
|
@@ -52,8 +53,10 @@ declare const __VLS_export: import("vue").DefineComponent<{
|
|
|
52
53
|
rowSelection: import("effect/Schema").optional<import("effect/Schema").Record$<typeof import("effect/Schema").String, typeof import("effect/Schema").Boolean>>;
|
|
53
54
|
}>>;
|
|
54
55
|
}> | undefined) => any;
|
|
56
|
+
"update:rows": (value: unknown[]) => any;
|
|
55
57
|
}, string, import("vue").PublicProps, Readonly<{
|
|
56
58
|
config?: TableConfigValue;
|
|
59
|
+
rows?: Array<unknown>;
|
|
57
60
|
}> & Readonly<{
|
|
58
61
|
"onUpdate:config"?: ((value: Readonly<{
|
|
59
62
|
kind: "shwfed.component.table";
|
|
@@ -94,4 +97,5 @@ declare const __VLS_export: import("vue").DefineComponent<{
|
|
|
94
97
|
rowSelection: import("effect/Schema").optional<import("effect/Schema").Record$<typeof import("effect/Schema").String, typeof import("effect/Schema").Boolean>>;
|
|
95
98
|
}>>;
|
|
96
99
|
}> | undefined) => any) | undefined;
|
|
100
|
+
"onUpdate:rows"?: ((value: unknown[]) => any) | undefined;
|
|
97
101
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -13,7 +13,7 @@ import { useVirtualizer } from "@tanstack/vue-virtual";
|
|
|
13
13
|
import { Effect, Fiber, Option } from "effect";
|
|
14
14
|
import { Fetch } from "fx-fetch";
|
|
15
15
|
import { Pagination } from "reka-ui/namespaced";
|
|
16
|
-
import { computed, defineComponent, h, onMounted, ref, watch } from "vue";
|
|
16
|
+
import { computed, defineComponent, h, onMounted, ref, toRaw, watch } from "vue";
|
|
17
17
|
import { useI18n } from "vue-i18n";
|
|
18
18
|
import { celBindings, provideCELContext, injectCELContext } from "../../utils/cel-context";
|
|
19
19
|
import { getLocalizedText } from "../../share/locale";
|
|
@@ -29,7 +29,7 @@ import { provideEventTarget } from "../../share/event-bus";
|
|
|
29
29
|
import { findColumn } from "./utils/resolve";
|
|
30
30
|
import { interpolateMarkdown } from "./utils/runtime";
|
|
31
31
|
const config = defineModel("config", { type: Object });
|
|
32
|
-
const rowData =
|
|
32
|
+
const rowData = defineModel("rows", { type: Array, ...{ default: () => [] } });
|
|
33
33
|
const serverTotal = ref(void 0);
|
|
34
34
|
const isFetching = ref(false);
|
|
35
35
|
const queryState = ref({});
|
|
@@ -301,6 +301,14 @@ async function resetQuery() {
|
|
|
301
301
|
queryState.value = {};
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
|
+
function cloneRow(row) {
|
|
305
|
+
if (!row || typeof row !== "object") return row;
|
|
306
|
+
try {
|
|
307
|
+
return structuredClone(toRaw(row));
|
|
308
|
+
} catch {
|
|
309
|
+
return { ...row };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
304
312
|
const tableInstanceId = config.value?.id ?? crypto.randomUUID();
|
|
305
313
|
provideTableInstanceId(tableInstanceId);
|
|
306
314
|
provideEventTarget(tableInstanceId, {
|
|
@@ -312,6 +320,24 @@ provideEventTarget(tableInstanceId, {
|
|
|
312
320
|
"search": () => Effect.promise(fetchDataSource),
|
|
313
321
|
"clear-selection": () => Effect.sync(() => {
|
|
314
322
|
tableApi.resetRowSelection(true);
|
|
323
|
+
}),
|
|
324
|
+
// Local row edits — mutate `rowData` in place, no server round-trip. Each
|
|
325
|
+
// re-assigns the array so the `rows` model re-emits and a host (the
|
|
326
|
+
// `table` form field) sees the live list. Intended for client-side data:
|
|
327
|
+
// with server pagination (`dataSource.total`) `rowData` is only the
|
|
328
|
+
// current page, so a fetch will overwrite these edits.
|
|
329
|
+
"add-row": () => Effect.sync(() => {
|
|
330
|
+
rowData.value = [...rowData.value, {}];
|
|
331
|
+
}),
|
|
332
|
+
"duplicate-selected": () => Effect.sync(() => {
|
|
333
|
+
const clones = tableApi.getSelectedRowModel().rows.map((r) => cloneRow(r.original));
|
|
334
|
+
if (clones.length > 0) rowData.value = [...rowData.value, ...clones];
|
|
335
|
+
}),
|
|
336
|
+
"delete-selected": () => Effect.sync(() => {
|
|
337
|
+
const drop = new Set(tableApi.getSelectedRowModel().rows.map((r) => r.index));
|
|
338
|
+
if (drop.size === 0) return;
|
|
339
|
+
rowData.value = rowData.value.filter((_, i) => !drop.has(i));
|
|
340
|
+
tableApi.resetRowSelection(true);
|
|
315
341
|
})
|
|
316
342
|
});
|
|
317
343
|
const queryRef = ref(null);
|
|
@@ -12,6 +12,7 @@ declare const _default: typeof __VLS_export;
|
|
|
12
12
|
export default _default;
|
|
13
13
|
declare const __VLS_export: import("vue").DefineComponent<{
|
|
14
14
|
config?: TableConfigValue;
|
|
15
|
+
rows?: Array<unknown>;
|
|
15
16
|
}, import("@tanstack/vue-table").Table<unknown>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
17
|
"update:config": (value: Readonly<{
|
|
17
18
|
kind: "shwfed.component.table";
|
|
@@ -52,8 +53,10 @@ declare const __VLS_export: import("vue").DefineComponent<{
|
|
|
52
53
|
rowSelection: import("effect/Schema").optional<import("effect/Schema").Record$<typeof import("effect/Schema").String, typeof import("effect/Schema").Boolean>>;
|
|
53
54
|
}>>;
|
|
54
55
|
}> | undefined) => any;
|
|
56
|
+
"update:rows": (value: unknown[]) => any;
|
|
55
57
|
}, string, import("vue").PublicProps, Readonly<{
|
|
56
58
|
config?: TableConfigValue;
|
|
59
|
+
rows?: Array<unknown>;
|
|
57
60
|
}> & Readonly<{
|
|
58
61
|
"onUpdate:config"?: ((value: Readonly<{
|
|
59
62
|
kind: "shwfed.component.table";
|
|
@@ -94,4 +97,5 @@ declare const __VLS_export: import("vue").DefineComponent<{
|
|
|
94
97
|
rowSelection: import("effect/Schema").optional<import("effect/Schema").Record$<typeof import("effect/Schema").String, typeof import("effect/Schema").Boolean>>;
|
|
95
98
|
}>>;
|
|
96
99
|
}> | undefined) => any) | undefined;
|
|
100
|
+
"onUpdate:rows"?: ((value: unknown[]) => any) | undefined;
|
|
97
101
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -23,6 +23,18 @@ export declare const metadata: {
|
|
|
23
23
|
readonly id: "clear-selection";
|
|
24
24
|
readonly name: "清空所有选中行";
|
|
25
25
|
readonly icon: "fluent:select-all-off-20-regular";
|
|
26
|
+
}, {
|
|
27
|
+
readonly id: "add-row";
|
|
28
|
+
readonly name: "新增一行";
|
|
29
|
+
readonly icon: "fluent:add-20-regular";
|
|
30
|
+
}, {
|
|
31
|
+
readonly id: "duplicate-selected";
|
|
32
|
+
readonly name: "复制选中行";
|
|
33
|
+
readonly icon: "fluent:copy-20-regular";
|
|
34
|
+
}, {
|
|
35
|
+
readonly id: "delete-selected";
|
|
36
|
+
readonly name: "删除选中行";
|
|
37
|
+
readonly icon: "fluent:delete-20-regular";
|
|
26
38
|
}];
|
|
27
39
|
};
|
|
28
40
|
declare const InitialState: Schema.Struct<{
|
|
@@ -13,11 +13,16 @@ export const metadata = {
|
|
|
13
13
|
// Atomic operations — compose them on one button (ordered triggers) to get
|
|
14
14
|
// the legacy combined behaviours. Only `search` actually fetches data;
|
|
15
15
|
// `reset-query` / `reset-pagination` / `clear-selection` just prepare state.
|
|
16
|
+
// The `*-row` / `*-selected` trio edits the row list locally — no server
|
|
17
|
+
// round-trip — for tables wired up as an editable surface.
|
|
16
18
|
operations: [
|
|
17
19
|
{ id: "reset-query", name: "\u91CD\u7F6E\u641C\u7D22\u6761\u4EF6", icon: "fluent:arrow-reset-20-regular" },
|
|
18
20
|
{ id: "reset-pagination", name: "\u91CD\u7F6E\u5206\u9875\u5E76\u6267\u884C\u641C\u7D22", icon: "fluent:arrow-previous-20-regular" },
|
|
19
21
|
{ id: "search", name: "\u6267\u884C\u641C\u7D22", icon: "fluent:search-20-regular" },
|
|
20
|
-
{ id: "clear-selection", name: "\u6E05\u7A7A\u6240\u6709\u9009\u4E2D\u884C", icon: "fluent:select-all-off-20-regular" }
|
|
22
|
+
{ id: "clear-selection", name: "\u6E05\u7A7A\u6240\u6709\u9009\u4E2D\u884C", icon: "fluent:select-all-off-20-regular" },
|
|
23
|
+
{ id: "add-row", name: "\u65B0\u589E\u4E00\u884C", icon: "fluent:add-20-regular" },
|
|
24
|
+
{ id: "duplicate-selected", name: "\u590D\u5236\u9009\u4E2D\u884C", icon: "fluent:copy-20-regular" },
|
|
25
|
+
{ id: "delete-selected", name: "\u5220\u9664\u9009\u4E2D\u884C", icon: "fluent:delete-20-regular" }
|
|
21
26
|
]
|
|
22
27
|
};
|
|
23
28
|
const SortItem = Schema.Struct({
|
|
@@ -229,12 +229,16 @@ http.get("https://api.example.com/users")
|
|
|
229
229
|
.query(form)
|
|
230
230
|
.query("page", string(pageIndex))
|
|
231
231
|
|
|
232
|
-
// Set request body
|
|
232
|
+
// Set request body. An object serializes to JSON; set the Content-Type
|
|
233
|
+
// header yourself. Optional values are unwrapped — a present `.?` field
|
|
234
|
+
// keeps its value, an absent one becomes `null` (the key is kept):
|
|
233
235
|
http.post("https://api.example.com/users")
|
|
234
|
-
.
|
|
236
|
+
.header("Content-Type", "application/json")
|
|
237
|
+
.body({"name": "Alice", "channel": query.?channel})
|
|
235
238
|
|
|
236
239
|
// Full example — the expression returns an HttpRequest; the host sends it:
|
|
237
240
|
http.post("https://api.example.com/users")
|
|
241
|
+
.header("Content-Type", "application/json")
|
|
238
242
|
.header("Authorization", "Bearer " + token)
|
|
239
243
|
.body({"name": "Alice"})
|
|
240
244
|
```
|
|
@@ -15,7 +15,13 @@ export declare class HttpRequestBuilder {
|
|
|
15
15
|
header(name: string, value: string): this;
|
|
16
16
|
query(key: string, value: string | number): this;
|
|
17
17
|
query(record: Record<string, unknown>): this;
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Set the request body. A pre-built `FormData` / `Blob` / `ArrayBuffer` /
|
|
20
|
+
* string is used verbatim; anything else is normalized (see
|
|
21
|
+
* `normalizeBody`) and JSON-serialized. The `Content-Type` header is *not*
|
|
22
|
+
* set here — the caller adds `.header('Content-Type', …)` explicitly.
|
|
23
|
+
*/
|
|
24
|
+
body(data: unknown): this;
|
|
19
25
|
/**
|
|
20
26
|
* A structural snapshot of the request this builder will issue — an equal
|
|
21
27
|
* snapshot means an equal HTTP request. Handy for change-detection, e.g.
|
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
2
|
import {
|
|
3
3
|
Fetch,
|
|
4
4
|
Request as FxRequest,
|
|
5
5
|
Response as FxResponse
|
|
6
6
|
} from "fx-fetch";
|
|
7
|
+
import { Decimal } from "./decimal.js";
|
|
8
|
+
function normalizeBody(value) {
|
|
9
|
+
if (value === null || value === void 0) return value;
|
|
10
|
+
if (value instanceof Decimal) return value.toNumber();
|
|
11
|
+
if (Option.isOption(value)) {
|
|
12
|
+
return Option.isSome(value) ? normalizeBody(value.value) : null;
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(value)) return value.map(normalizeBody);
|
|
15
|
+
if (typeof value === "object") {
|
|
16
|
+
const proto = Object.getPrototypeOf(value);
|
|
17
|
+
if (proto !== Object.prototype && proto !== null) return value;
|
|
18
|
+
const out = {};
|
|
19
|
+
for (const key of Object.keys(value)) {
|
|
20
|
+
out[key] = normalizeBody(value[key]);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
7
26
|
function extractFilename(response, url) {
|
|
8
27
|
const disposition = FxResponse.getHeader(response, "content-disposition");
|
|
9
28
|
if (disposition) {
|
|
@@ -53,11 +72,15 @@ export class HttpRequestBuilder {
|
|
|
53
72
|
}
|
|
54
73
|
throw new Error(`query: nested object values are not supported (key: "${key}")`);
|
|
55
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Set the request body. A pre-built `FormData` / `Blob` / `ArrayBuffer` /
|
|
77
|
+
* string is used verbatim; anything else is normalized (see
|
|
78
|
+
* `normalizeBody`) and JSON-serialized. The `Content-Type` header is *not*
|
|
79
|
+
* set here — the caller adds `.header('Content-Type', …)` explicitly.
|
|
80
|
+
*/
|
|
56
81
|
body(data) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.#headers.push(["Content-Type", "application/json"]);
|
|
60
|
-
}
|
|
82
|
+
const value = normalizeBody(data);
|
|
83
|
+
this.#body = value instanceof FormData || value instanceof Blob || value instanceof ArrayBuffer || typeof value === "string" ? value : JSON.stringify(value);
|
|
61
84
|
return this;
|
|
62
85
|
}
|
|
63
86
|
/**
|