@shwfed/config 2.3.3 → 2.3.5

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 (44) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/actions/components/group.d.vue.ts +12 -10
  3. package/dist/runtime/components/actions/components/group.vue +47 -13
  4. package/dist/runtime/components/actions/components/group.vue.d.ts +12 -10
  5. package/dist/runtime/components/actions/config.d.vue.ts +13 -11
  6. package/dist/runtime/components/actions/config.vue +252 -35
  7. package/dist/runtime/components/actions/config.vue.d.ts +13 -11
  8. package/dist/runtime/components/actions/items/2026-05-21/com.shwfed.actions.item.markdown/config.d.vue.ts +37 -0
  9. package/dist/runtime/components/actions/items/2026-05-21/com.shwfed.actions.item.markdown/config.vue +61 -0
  10. package/dist/runtime/components/actions/items/2026-05-21/com.shwfed.actions.item.markdown/config.vue.d.ts +37 -0
  11. package/dist/runtime/components/actions/items/2026-05-21/com.shwfed.actions.item.markdown/runtime.d.vue.ts +7 -0
  12. package/dist/runtime/components/actions/items/2026-05-21/com.shwfed.actions.item.markdown/runtime.vue +22 -0
  13. package/dist/runtime/components/actions/items/2026-05-21/com.shwfed.actions.item.markdown/runtime.vue.d.ts +7 -0
  14. package/dist/runtime/components/actions/items/2026-05-21/com.shwfed.actions.item.markdown/schema.d.ts +23 -0
  15. package/dist/runtime/components/actions/items/2026-05-21/com.shwfed.actions.item.markdown/schema.js +29 -0
  16. package/dist/runtime/components/actions/schema.d.ts +400 -193
  17. package/dist/runtime/components/actions/schema.js +22 -4
  18. package/dist/runtime/components/actions/utils/resolve.d.ts +15 -0
  19. package/dist/runtime/components/actions/utils/resolve.js +45 -0
  20. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.actions/config.d.vue.ts +10 -8
  21. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.actions/config.vue.d.ts +10 -8
  22. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.actions/runtime.d.vue.ts +10 -8
  23. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.actions/runtime.vue.d.ts +10 -8
  24. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.actions/schema.d.ts +115 -57
  25. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.d.vue.ts +10 -8
  26. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.vue.d.ts +10 -8
  27. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.d.vue.ts +10 -8
  28. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.vue.d.ts +10 -8
  29. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/schema.d.ts +120 -61
  30. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.actions/config.d.vue.ts +10 -8
  31. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.actions/config.vue.d.ts +10 -8
  32. package/dist/runtime/components/form/fields/2026-04-24/com.shwfed.form.field.actions/schema.d.ts +115 -57
  33. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.actions/schema.d.ts +115 -57
  34. package/dist/runtime/components/table/columns/2026-05-20/com.shwfed.table.column.combobox-single/config.d.vue.ts +2 -2
  35. package/dist/runtime/components/table/columns/2026-05-20/com.shwfed.table.column.combobox-single/config.vue.d.ts +2 -2
  36. package/dist/runtime/components/table/columns/2026-05-20/com.shwfed.table.column.combobox-single/runtime.vue +4 -4
  37. package/dist/runtime/components/table/schema.d.ts +125 -65
  38. package/dist/runtime/components/table/utils/row-reorder.js +16 -5
  39. package/dist/runtime/vendor/cel-js/CLAUDE.md +5 -1
  40. package/dist/runtime/vendor/cel-js/PROMPT.md +19 -0
  41. package/dist/runtime/vendor/cel-js/lib/evaluator.js +2 -0
  42. package/dist/runtime/vendor/cel-js/lib/form-builtins.d.ts +2 -0
  43. package/dist/runtime/vendor/cel-js/lib/form-builtins.js +58 -0
  44. package/package.json +1 -1
@@ -132,7 +132,7 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
132
132
  readonly size: "default" | "sm" | "xs";
133
133
  readonly style?: string | undefined;
134
134
  readonly gap: number;
135
- readonly items: readonly ({
135
+ readonly items: readonly (import("../actions/schema.js").RegistryItemValue | {
136
136
  readonly disabled?: string | undefined;
137
137
  readonly id: string;
138
138
  readonly title: readonly [{
@@ -165,8 +165,7 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
165
165
  readonly message: string;
166
166
  }[]];
167
167
  readonly icon?: string | undefined;
168
- readonly groupId: string;
169
- readonly items: readonly {
168
+ readonly items: readonly (import("../actions/schema.js").RegistrySubItemValue | {
170
169
  readonly disabled?: string | undefined;
171
170
  readonly id: string;
172
171
  readonly title: readonly [{
@@ -187,7 +186,9 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
187
186
  }[]] | undefined;
188
187
  readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
189
188
  readonly action?: any;
190
- }[];
189
+ })[];
190
+ readonly groupId: string;
191
+ readonly hideTitle?: boolean | undefined;
191
192
  })[];
192
193
  readonly groups: readonly {
193
194
  readonly id: string;
@@ -310,63 +311,121 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
310
311
  id: Schema.refine<string, typeof Schema.String>;
311
312
  variant: Schema.optional<Schema.Literal<["default", "primary", "destructive", "ghost", "link"]>>;
312
313
  }>>;
313
- items: Schema.Array$<Schema.Union<[Schema.Struct<{
314
- id: Schema.refine<string, typeof Schema.String>;
315
- groupId: Schema.refine<string, typeof Schema.String>;
316
- title: Schema.TupleType<readonly [Schema.Struct<{
317
- locale: Schema.Literal<["zh"]>;
318
- message: Schema.SchemaClass<string, string, never>;
319
- }>], [Schema.Struct<{
320
- locale: Schema.Literal<["ja", "en", "ko"]>;
321
- message: Schema.SchemaClass<string, string, never>;
322
- }>]>;
323
- tooltip: Schema.optional<Schema.TupleType<readonly [Schema.Struct<{
324
- locale: Schema.Literal<["zh"]>;
325
- message: Schema.SchemaClass<string, string, never>;
326
- }>], [Schema.Struct<{
327
- locale: Schema.Literal<["ja", "en", "ko"]>;
328
- message: Schema.SchemaClass<string, string, never>;
329
- }>]>>;
330
- icon: Schema.optional<Schema.SchemaClass<string, string, never>>;
331
- hidden: Schema.optional<Schema.Schema<string, string, never>>;
332
- disabled: Schema.optional<Schema.Schema<string, string, never>>;
333
- variant: Schema.optional<Schema.Literal<["default", "primary", "destructive", "ghost", "link"]>>;
334
- hideTitle: Schema.optional<Schema.SchemaClass<boolean, boolean, never>>;
335
- action: Schema.optional<Schema.Schema<any, any, never>>;
336
- }>, Schema.Struct<{
337
- id: Schema.refine<string, typeof Schema.String>;
338
- groupId: Schema.refine<string, typeof Schema.String>;
339
- title: Schema.TupleType<readonly [Schema.Struct<{
340
- locale: Schema.Literal<["zh"]>;
341
- message: Schema.SchemaClass<string, string, never>;
342
- }>], [Schema.Struct<{
343
- locale: Schema.Literal<["ja", "en", "ko"]>;
344
- message: Schema.SchemaClass<string, string, never>;
345
- }>]>;
346
- icon: Schema.optional<Schema.SchemaClass<string, string, never>>;
347
- items: Schema.Array$<Schema.Struct<{
348
- id: Schema.refine<string, typeof Schema.String>;
349
- title: Schema.TupleType<readonly [Schema.Struct<{
350
- locale: Schema.Literal<["zh"]>;
351
- message: Schema.SchemaClass<string, string, never>;
352
- }>], [Schema.Struct<{
353
- locale: Schema.Literal<["ja", "en", "ko"]>;
354
- message: Schema.SchemaClass<string, string, never>;
355
- }>]>;
356
- tooltip: Schema.optional<Schema.TupleType<readonly [Schema.Struct<{
357
- locale: Schema.Literal<["zh"]>;
358
- message: Schema.SchemaClass<string, string, never>;
359
- }>], [Schema.Struct<{
360
- locale: Schema.Literal<["ja", "en", "ko"]>;
361
- message: Schema.SchemaClass<string, string, never>;
362
- }>]>>;
363
- icon: Schema.optional<Schema.SchemaClass<string, string, never>>;
364
- hidden: Schema.optional<Schema.Schema<string, string, never>>;
365
- disabled: Schema.optional<Schema.Schema<string, string, never>>;
366
- variant: Schema.optional<Schema.Literal<["default", "primary", "destructive", "ghost", "link"]>>;
367
- action: Schema.optional<Schema.Schema<any, any, never>>;
368
- }>>;
369
- }>]>>;
314
+ items: Schema.Array$<Schema.Schema<import("../actions/schema.js").RegistryItemValue | {
315
+ readonly disabled?: string | undefined;
316
+ readonly id: string;
317
+ readonly title: readonly [{
318
+ readonly locale: "zh";
319
+ readonly message: string;
320
+ }, ...{
321
+ readonly locale: "en" | "ja" | "ko";
322
+ readonly message: string;
323
+ }[]];
324
+ readonly icon?: string | undefined;
325
+ readonly hidden?: string | undefined;
326
+ readonly tooltip?: readonly [{
327
+ readonly locale: "zh";
328
+ readonly message: string;
329
+ }, ...{
330
+ readonly locale: "en" | "ja" | "ko";
331
+ readonly message: string;
332
+ }[]] | undefined;
333
+ readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
334
+ readonly action?: any;
335
+ readonly groupId: string;
336
+ readonly hideTitle?: boolean | undefined;
337
+ } | {
338
+ readonly id: string;
339
+ readonly title: readonly [{
340
+ readonly locale: "zh";
341
+ readonly message: string;
342
+ }, ...{
343
+ readonly locale: "en" | "ja" | "ko";
344
+ readonly message: string;
345
+ }[]];
346
+ readonly icon?: string | undefined;
347
+ readonly items: readonly (import("../actions/schema.js").RegistrySubItemValue | {
348
+ readonly disabled?: string | undefined;
349
+ readonly id: string;
350
+ readonly title: readonly [{
351
+ readonly locale: "zh";
352
+ readonly message: string;
353
+ }, ...{
354
+ readonly locale: "en" | "ja" | "ko";
355
+ readonly message: string;
356
+ }[]];
357
+ readonly icon?: string | undefined;
358
+ readonly hidden?: string | undefined;
359
+ readonly tooltip?: readonly [{
360
+ readonly locale: "zh";
361
+ readonly message: string;
362
+ }, ...{
363
+ readonly locale: "en" | "ja" | "ko";
364
+ readonly message: string;
365
+ }[]] | undefined;
366
+ readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
367
+ readonly action?: any;
368
+ })[];
369
+ readonly groupId: string;
370
+ readonly hideTitle?: boolean | undefined;
371
+ }, import("../actions/schema.js").RegistryItemValue | {
372
+ readonly id: string;
373
+ readonly title: readonly [{
374
+ readonly locale: "zh";
375
+ readonly message: string;
376
+ }, ...{
377
+ readonly locale: "en" | "ja" | "ko";
378
+ readonly message: string;
379
+ }[]];
380
+ readonly groupId: string;
381
+ readonly disabled?: string | undefined;
382
+ readonly icon?: string | undefined;
383
+ readonly hidden?: string | undefined;
384
+ readonly tooltip?: readonly [{
385
+ readonly locale: "zh";
386
+ readonly message: string;
387
+ }, ...{
388
+ readonly locale: "en" | "ja" | "ko";
389
+ readonly message: string;
390
+ }[]] | undefined;
391
+ readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
392
+ readonly action?: any;
393
+ readonly hideTitle?: boolean | undefined;
394
+ } | {
395
+ readonly id: string;
396
+ readonly title: readonly [{
397
+ readonly locale: "zh";
398
+ readonly message: string;
399
+ }, ...{
400
+ readonly locale: "en" | "ja" | "ko";
401
+ readonly message: string;
402
+ }[]];
403
+ readonly items: readonly (import("../actions/schema.js").RegistrySubItemValue | {
404
+ readonly id: string;
405
+ readonly title: readonly [{
406
+ readonly locale: "zh";
407
+ readonly message: string;
408
+ }, ...{
409
+ readonly locale: "en" | "ja" | "ko";
410
+ readonly message: string;
411
+ }[]];
412
+ readonly disabled?: string | undefined;
413
+ readonly icon?: string | undefined;
414
+ readonly hidden?: string | undefined;
415
+ readonly tooltip?: readonly [{
416
+ readonly locale: "zh";
417
+ readonly message: string;
418
+ }, ...{
419
+ readonly locale: "en" | "ja" | "ko";
420
+ readonly message: string;
421
+ }[]] | undefined;
422
+ readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
423
+ readonly action?: any;
424
+ })[];
425
+ readonly groupId: string;
426
+ readonly icon?: string | undefined;
427
+ readonly hideTitle?: boolean | undefined;
428
+ }, never>>;
370
429
  }>>;
371
430
  query: Schema.optional<Schema.refine<{
372
431
  readonly initial?: {
@@ -512,7 +571,7 @@ export declare function createTableConfig(body: Omit<Schema.Schema.Type<ReturnTy
512
571
  readonly size: "default" | "sm" | "xs";
513
572
  readonly style?: string | undefined;
514
573
  readonly gap: number;
515
- readonly items: readonly ({
574
+ readonly items: readonly (import("../actions/schema.js").RegistryItemValue | {
516
575
  readonly disabled?: string | undefined;
517
576
  readonly id: string;
518
577
  readonly title: readonly [{
@@ -545,8 +604,7 @@ export declare function createTableConfig(body: Omit<Schema.Schema.Type<ReturnTy
545
604
  readonly message: string;
546
605
  }[]];
547
606
  readonly icon?: string | undefined;
548
- readonly groupId: string;
549
- readonly items: readonly {
607
+ readonly items: readonly (import("../actions/schema.js").RegistrySubItemValue | {
550
608
  readonly disabled?: string | undefined;
551
609
  readonly id: string;
552
610
  readonly title: readonly [{
@@ -567,7 +625,9 @@ export declare function createTableConfig(body: Omit<Schema.Schema.Type<ReturnTy
567
625
  }[]] | undefined;
568
626
  readonly variant?: "default" | "link" | "destructive" | "primary" | "ghost" | undefined;
569
627
  readonly action?: any;
570
- }[];
628
+ })[];
629
+ readonly groupId: string;
630
+ readonly hideTitle?: boolean | undefined;
571
631
  })[];
572
632
  readonly groups: readonly {
573
633
  readonly id: string;
@@ -5,7 +5,6 @@ import {
5
5
  dropTargetForElements
6
6
  } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
7
7
  import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview";
8
- import { preserveOffsetOnSource } from "@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source";
9
8
  import {
10
9
  attachInstruction,
11
10
  extractInstruction
@@ -49,10 +48,22 @@ export function useRowReorder(options) {
49
48
  onGenerateDragPreview: ({ nativeSetDragImage, location, source }) => {
50
49
  setCustomNativeDragPreview({
51
50
  nativeSetDragImage,
52
- getOffset: preserveOffsetOnSource({
53
- element: source.element,
54
- input: location.current.input
55
- }),
51
+ // Anchor the preview to the grip's rect, not the row's. The row is
52
+ // virtualized (`position: absolute` + `translate3d`) so its
53
+ // bounding box shifts with vertical scroll and `preserveOffsetOnSource`
54
+ // pulls the preview off the pointer once the table has been
55
+ // scrolled. The grip is always rendered exactly where the user
56
+ // clicked AND sits at the row's leading edge, so offsetting from
57
+ // it keeps the start of the line pinned under the cursor in
58
+ // every scroll state.
59
+ getOffset: ({ container }) => {
60
+ const containerRect = container.getBoundingClientRect();
61
+ const handleRect = handleEl.getBoundingClientRect();
62
+ return {
63
+ x: Math.max(0, Math.min(location.current.input.clientX - handleRect.left, containerRect.width)),
64
+ y: Math.max(0, Math.min(location.current.input.clientY - handleRect.top, containerRect.height))
65
+ };
66
+ },
56
67
  render: ({ container }) => {
57
68
  const src = source.element;
58
69
  const rect = src.getBoundingClientRect();
@@ -22,6 +22,9 @@ The custom `Optional` class has been replaced with Effect's `Option` type (`impo
22
22
 
23
23
  An `http` built-in has been added (`http-builtins.ts`, `http-builder.ts`) — not from upstream. It registers the `http` constant and the `http` / `HttpRequest` types on `globalRegistry`, so `http.get(url).header(...).body(...)` expressions type-check and evaluate in every `Environment`. A CEL expression only builds an `HttpRequestBuilder` — a pure description of a request; it never issues one. Both terminal methods, `.json()` and `.file()`, are dispatched by the host on the returned builder (neither is a CEL method), so expression evaluation stays free of IO. Endpoints must be absolute URLs — there is no base-URL resolution. Depends on `fx-fetch`. The host can register process-wide default headers via `HttpRequestBuilder.setDefaultHeader(name, valueOrGetter)` / `clearDefaultHeader(name)`; they are merged in at `#buildRequest()` time, with case-insensitive precedence to an explicit `.header(...)` on the builder. A getter that returns `null` / `undefined` / `''` skips the header for that request, so the host can source values from live refs (e.g. the active i18n locale for `Accept-Language`) without baking in stale snapshots.
24
24
 
25
+ A `form(dyn): FormData` built-in has been added (`form-builtins.ts`) — not from upstream. It registers the `FormData` type on `globalRegistry` and a global `form` function that turns a CEL map into a native `FormData`, so authors can write `http.post(url).body(form({"file": myFile, "name": "Alice"}))` (`.body()` already passes `FormData` through verbatim and `fetch` sets the multipart boundary). Value coercion mirrors `.query()`'s flat-record semantics: `null` / `undefined` / `Option.None` skip the key; `Option.Some(x)` recurses on `x`; `File` / `Blob` pass through (preserves the `File` filename); `Decimal` → `.toString()` (preserves precision past safe-integer); `Date` (`TZDate`) → `.toISOString()`; `bool` → `'true'` / `'false'`; arrays append one entry per element; nested objects throw (`multipart/form-data` is flat — same rule as `.query(dyn)`). Top-level input must be a map literal / record (Map or plain object); passing an array or scalar throws.
26
+ The `__proto__` / `constructor` / `prototype` keys are skipped on the top-level record, mirroring `safeFromEntries`.
27
+
25
28
  A `JSON` built-in namespace has been added (`json-builtins.ts`) — not from upstream. It registers a `JSON` brand class and a same-named constant on `globalRegistry`, so `JSON.parse(string): dyn` and `JSON.stringify(dyn): string` resolve in every `Environment`. The CEL-side surface mirrors the JS globals deliberately. `JSON.parse` is a thin wrapper over `globalThis.JSON.parse` — numbers come back as plain JS `number` (the evaluator coerces via `Decimal.from` at use time). `JSON.stringify` pre-walks the value via `normalizeForJSON` before handing it to `globalThis.JSON.stringify`: `Decimal` → JS number (via `.toNumber()` — loses precision past safe-integer range, deliberate trade-off for idiomatic JSON output), `Option.Some(x)` → `x`, `Option.None` → `null`, `Map` → plain object, `Date`/`TZDate` passes through so its built-in `toJSON` emits ISO 8601. The pre-walk is required because Effect's `Option` defines its own `toJSON()` that runs before any replacer would see it.
26
29
 
27
30
  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.
@@ -42,6 +45,7 @@ lib/
42
45
  ├── http-builtins.ts — `http` constant + http/HttpRequest types (local addition)
43
46
  ├── http-builder.ts — `HttpRequestBuilder` — what an http.* expression returns
44
47
  ├── json-builtins.ts — `JSON` constant + `JSON.parse` / `JSON.stringify` (local addition)
48
+ ├── form-builtins.ts — `form(map): FormData` global + `FormData` type (local addition)
45
49
  ├── decimal.ts — Decimal class (arbitrary precision)
46
50
  ├── optional.ts — Optional type support (uses Effect Option)
47
51
  ├── serialize.ts — AST back to CEL string
@@ -73,7 +77,7 @@ Internal-only (not re-exported from `index.js`): `parse`, standalone `evaluate`/
73
77
 
74
78
  Primitives: `string`, `bool`, `number`, `bytes`, `null_type`, `type`
75
79
  Composites: `list<T>`, `map<K, V>`, `optional<T>`, `dyn`, `message`
76
- Custom: `Date` (backed by `TZDate` from `@date-fns/tz`), `URL` (backed by the native `URL`; method `URL.param(string): string` reads a search param), `File` (native), `http` / `HttpRequest` (local addition — see `http-builtins.ts`)
80
+ Custom: `Date` (backed by `TZDate` from `@date-fns/tz`), `URL` (backed by the native `URL`; method `URL.param(string): string` reads a search param), `File` (native), `FormData` (native — see `form-builtins.ts`), `http` / `HttpRequest` (local addition — see `http-builtins.ts`)
77
81
 
78
82
  ## Function Signature Format
79
83
 
@@ -174,6 +174,25 @@ unwrapped first so the output is plain JSON — numbers (CEL's `Decimal`) become
174
174
  JS numbers, optionals become their value (or `null` when empty), and `Date`
175
175
  values become ISO 8601 strings.
176
176
 
177
+ ### FormData
178
+ ```cel
179
+ // Build multipart/form-data for an upload:
180
+ http.post("https://api.example.com/files")
181
+ .body(form({"file": myFile, "name": "Alice", "tags": ["a", "b"]}))
182
+ ```
183
+ `form(map): FormData` turns a flat record into a native `FormData`. Pass it
184
+ straight to `.body(...)` — `fetch` sets the multipart boundary, so you don't
185
+ need a `Content-Type` header.
186
+
187
+ Value coercion is flat (same rules as `.query(...)`):
188
+ - `null` / `undefined` / absent `.?` values → key is skipped
189
+ - `Option.Some(x)` → unwrapped to `x`
190
+ - `File` / `Blob` → appended as-is (the `File` filename is preserved)
191
+ - numbers / booleans / dates → coerced to string (`Date` → ISO 8601)
192
+ - arrays → one entry per element under the same key
193
+ - nested objects / maps → error (multipart is flat — use `JSON.stringify(...)`
194
+ on the value if you need a structured field)
195
+
177
196
  ### Dates
178
197
  ```cel
179
198
  date("2023-06-15") // create a Date
@@ -4,6 +4,7 @@ import { evaluationError } from "./errors.js";
4
4
  import { registerFunctions } from "./functions.js";
5
5
  import { registerHttpBuiltins } from "./http-builtins.js";
6
6
  import { registerJSONBuiltins } from "./json-builtins.js";
7
+ import { registerFormBuiltins } from "./form-builtins.js";
7
8
  import { registerMacros } from "./macros.js";
8
9
  import { registerOverloads } from "./overloads.js";
9
10
  import { TypeChecker } from "./type-checker.js";
@@ -16,6 +17,7 @@ registerOverloads(globalRegistry);
16
17
  registerMacros(globalRegistry);
17
18
  registerHttpBuiltins(globalRegistry);
18
19
  registerJSONBuiltins(globalRegistry);
20
+ registerFormBuiltins(globalRegistry);
19
21
  class Environment {
20
22
  #registry;
21
23
  #evaluator;
@@ -0,0 +1,2 @@
1
+ import type { Registry } from './registry.js.js';
2
+ export declare function registerFormBuiltins(registry: Registry): void;
@@ -0,0 +1,58 @@
1
+ import { Option } from "effect";
2
+ import { evaluationError } from "./errors.js";
3
+ import { Decimal } from "./decimal.js";
4
+ function coerceScalar(value) {
5
+ if (value instanceof Blob) return value;
6
+ if (value instanceof Decimal) return value.toString();
7
+ if (value instanceof Date) return value.toISOString();
8
+ if (typeof value === "string") return value;
9
+ if (typeof value === "boolean") return value ? "true" : "false";
10
+ if (typeof value === "number" || typeof value === "bigint") return String(value);
11
+ throw evaluationError(
12
+ "invalid_form_value",
13
+ `form(): unsupported value type ${Object.prototype.toString.call(value)}`
14
+ );
15
+ }
16
+ function appendEntry(fd, key, value) {
17
+ if (value === null || value === void 0) return;
18
+ if (Option.isOption(value)) {
19
+ if (Option.isSome(value)) appendEntry(fd, key, value.value);
20
+ return;
21
+ }
22
+ if (Array.isArray(value)) {
23
+ for (const item of value) appendEntry(fd, key, item);
24
+ return;
25
+ }
26
+ if (value instanceof Blob) {
27
+ fd.append(key, value);
28
+ return;
29
+ }
30
+ if (typeof value === "object" && !(value instanceof Decimal) && !(value instanceof Date)) {
31
+ throw evaluationError(
32
+ "invalid_form_value",
33
+ `form(): nested object values are not supported (key: "${key}")`
34
+ );
35
+ }
36
+ fd.append(key, coerceScalar(value));
37
+ }
38
+ export function registerFormBuiltins(registry) {
39
+ registry.registerType("FormData", FormData);
40
+ registry.registerFunctionOverload("form(dyn): FormData", (record) => {
41
+ if (record === null || record === void 0) {
42
+ throw evaluationError("invalid_form_value", "form(): expected a map, got null");
43
+ }
44
+ const fd = new FormData();
45
+ if (record instanceof Map) {
46
+ for (const [k, v] of record) appendEntry(fd, String(k), v);
47
+ return fd;
48
+ }
49
+ if (typeof record !== "object" || Array.isArray(record)) {
50
+ throw evaluationError("invalid_form_value", "form(): expected a map");
51
+ }
52
+ for (const key of Object.keys(record)) {
53
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
54
+ appendEntry(fd, key, record[key]);
55
+ }
56
+ return fd;
57
+ });
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/config",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {