@shwfed/config 2.2.0 → 2.2.2

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 (41) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/form/FormUnitRenderer.d.vue.ts +9 -0
  3. package/dist/runtime/components/form/FormUnitRenderer.vue +117 -0
  4. package/dist/runtime/components/form/FormUnitRenderer.vue.d.ts +9 -0
  5. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.d.vue.ts +2 -0
  6. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue +31 -0
  7. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue.d.ts +2 -0
  8. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/runtime.vue +12 -2
  9. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.d.ts +1 -0
  10. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/schema.js +4 -0
  11. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.d.vue.ts +2 -0
  12. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue +31 -0
  13. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue.d.ts +2 -0
  14. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/runtime.vue +16 -8
  15. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.d.ts +1 -0
  16. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/schema.js +4 -0
  17. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.d.vue.ts +2 -2
  18. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue +1 -1
  19. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue.d.ts +2 -2
  20. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/row.vue +7 -104
  21. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.d.ts +1 -1
  22. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.d.vue.ts +53 -0
  23. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.vue +130 -0
  24. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/config.vue.d.ts +53 -0
  25. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.d.vue.ts +8 -0
  26. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.vue +107 -0
  27. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/runtime.vue.d.ts +8 -0
  28. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/schema.d.ts +54 -0
  29. package/dist/runtime/components/form/fields/2026-05-20/com.shwfed.form.field.collapsible/schema.js +47 -0
  30. package/dist/runtime/components/form/index.vue +16 -108
  31. package/dist/runtime/components/table/columns/2026-04-14/com.shwfed.table.column.number/runtime.vue +1 -0
  32. package/dist/runtime/plugins/i18n/index.js +8 -2
  33. package/dist/runtime/vendor/cel-js/CLAUDE.md +3 -1
  34. package/dist/runtime/vendor/cel-js/PROMPT.md +20 -0
  35. package/dist/runtime/vendor/cel-js/lib/http-builder.d.ts +2 -0
  36. package/dist/runtime/vendor/cel-js/lib/http-builder.js +24 -3
  37. package/dist/runtime/vendor/cel-js/lib/operators.js +139 -11
  38. package/dist/runtime/vendor/cel-js/lib/parser.d.ts +2 -1
  39. package/dist/runtime/vendor/cel-js/lib/parser.js +20 -3
  40. package/dist/runtime/vendor/cel-js/lib/serialize.js +5 -1
  41. package/package.json +1 -1
@@ -20,7 +20,9 @@ The `homogeneousAggregateLiterals` and `enableOptionalTypes` environment options
20
20
 
21
21
  The custom `Optional` class has been replaced with Effect's `Option` type (`import { Option } from 'effect'`). Internal helpers `optionalOf(value)` and `optionalValue(opt)` in `optional.js` handle the CEL-specific semantics (treating `undefined` as `None`, throwing `EvaluationError` on missing value access).
22
22
 
23
- An `http` built-in has been added (`http-builtins.ts`, `http-builder.ts`) — not from upstream. It registers the `http` constant and the `http` / `HttpRequest` types on `globalRegistry`, so `http.get(url).header(...).body(...)` expressions type-check and evaluate in every `Environment`. A CEL expression only builds an `HttpRequestBuilder` — a pure description of a request; it never issues one. Both terminal methods, `.json()` and `.file()`, are dispatched by the host on the returned builder (neither is a CEL method), so expression evaluation stays free of IO. Endpoints must be absolute URLs — there is no base-URL resolution. Depends on `fx-fetch`.
23
+ An `http` built-in has been added (`http-builtins.ts`, `http-builder.ts`) — not from upstream. It registers the `http` constant and the `http` / `HttpRequest` types on `globalRegistry`, so `http.get(url).header(...).body(...)` expressions type-check and evaluate in every `Environment`. A CEL expression only builds an `HttpRequestBuilder` — a pure description of a request; it never issues one. Both terminal methods, `.json()` and `.file()`, are dispatched by the host on the returned builder (neither is a CEL method), so expression evaluation stays free of IO. Endpoints must be absolute URLs — there is no base-URL resolution. Depends on `fx-fetch`. The host can register process-wide default headers via `HttpRequestBuilder.setDefaultHeader(name, valueOrGetter)` / `clearDefaultHeader(name)`; they are merged in at `#buildRequest()` time, with case-insensitive precedence to an explicit `.header(...)` on the builder. A getter that returns `null` / `undefined` / `''` skips the header for that request, so the host can source values from live refs (e.g. the active i18n locale for `Accept-Language`) without baking in stale snapshots.
24
+
25
+ Spread syntax (`...expr`) inside list and map literals — not from upstream. A new `ELLIPSIS` token (3-char lookahead in the lexer) and a `spread` AST op let `[1, ...a, 4]` and `{...a, "x": 1, ...b}` desugar inside the parent literal. The spread node's own `check`/`evaluate` are defensive stubs — `parsePrimary` never produces one, so `...` outside a list/map is an "unexpected token" parse error. List `args` shape is unchanged (`IASTNode[]`, with spread elements detected via `op === 'spread'`); map `args` becomes `([IASTNode, IASTNode] | [IASTNode])[]` — a length-1 tuple represents a spread entry, preserving positional ordering for override semantics (later writes win). The fast path is preserved: the `list`/`map` `check` swaps `evaluate` meta to `evaluateSpreadList`/`evaluateSpreadMap` only when a spread child is present; literals without spreads run the original `resolveAstArray`/`safeFromEntries` paths byte-for-byte. Spread map sources may be plain objects, `Map` instances, or registered message types (typed as `map<string, dyn>`); the `__proto__`/`constructor`/`prototype` skip from `safeFromEntries` is applied to spread sources too. Runtime errors fire on non-list/non-map sources (including `null`). `maxListElements` / `maxMapEntries` count a spread as one entry — runtime expansion bypasses those caps (deliberate trade-off). Call-argument spread (`f(...args)`) is **not** supported.
24
26
 
25
27
  ## Architecture
26
28
 
@@ -67,6 +67,26 @@ condition ? value_if_true : value_if_false
67
67
  [1, 2] + [3, 4] // [1, 2, 3, 4]
68
68
  ```
69
69
 
70
+ ### Spread (`...expr`)
71
+ A spread inlines another list inside a list literal or another map inside a
72
+ map literal. Order is preserved — a later entry (including a later spread)
73
+ overrides an earlier one when keys collide.
74
+ ```cel
75
+ [1, ...a, 4] // a flattens in position
76
+ [...a, ...b] // equivalent to a + b
77
+
78
+ {...a, "x": 1} // copy a, then set x to 1
79
+ {"x": 1, ...a} // set x, then let a override
80
+ {...a, ...b} // b overrides a on overlapping keys
81
+ {...a, "x": 1, ...b} // a, then x, then b — last write wins
82
+
83
+ [...[1, 2], 3, ...[4]] // [1, 2, 3, 4]
84
+ ```
85
+ Spread is only valid as a list element or map entry — `f(...args)` is not
86
+ supported (CEL resolves overloads by fixed arity). Spreading a non-list into
87
+ a list, or a non-map into a map (including `null`), is an error — at compile
88
+ time when statically known, at runtime when the source is `dyn`.
89
+
70
90
  ### Field access
71
91
  ```cel
72
92
  obj.field // access field on map/object
@@ -13,6 +13,8 @@ import { Effect } from 'effect';
13
13
  import { Fetch } from 'fx-fetch';
14
14
  export declare class HttpRequestBuilder {
15
15
  #private;
16
+ static setDefaultHeader(name: string, value: string | (() => string | null | undefined)): void;
17
+ static clearDefaultHeader(name: string): void;
16
18
  constructor(url: string, method: string);
17
19
  header(name: string, value: string): this;
18
20
  query(key: string, value: string | number): this;
@@ -38,6 +38,20 @@ export class HttpRequestBuilder {
38
38
  #headers = [];
39
39
  #queries = [];
40
40
  #body;
41
+ // Process-wide defaults applied at `#buildRequest()` time. Each entry is
42
+ // keyed by lowercased header name so an explicit `.header('X', …)` on the
43
+ // builder wins regardless of casing. A getter returning `null` / `undefined`
44
+ // / `''` skips the header for that request — lets the host source values
45
+ // from a live ref (e.g. the current i18n locale) without baking in stale
46
+ // snapshots.
47
+ static #defaultHeaders = /* @__PURE__ */ new Map();
48
+ static setDefaultHeader(name, value) {
49
+ const get = typeof value === "function" ? value : () => value;
50
+ HttpRequestBuilder.#defaultHeaders.set(name.toLowerCase(), { name, get });
51
+ }
52
+ static clearDefaultHeader(name) {
53
+ HttpRequestBuilder.#defaultHeaders.delete(name.toLowerCase());
54
+ }
41
55
  constructor(url, method) {
42
56
  this.#url = url;
43
57
  this.#method = method;
@@ -117,9 +131,16 @@ export class HttpRequestBuilder {
117
131
  url: this.#url,
118
132
  method: this.#method
119
133
  };
120
- if (this.#headers.length > 0) {
121
- const headers = {};
122
- for (const [k, v] of this.#headers) headers[k] = v;
134
+ const explicit = new Set(this.#headers.map(([k]) => k.toLowerCase()));
135
+ const headers = {};
136
+ for (const [k, v] of this.#headers) headers[k] = v;
137
+ for (const [key, { name, get }] of HttpRequestBuilder.#defaultHeaders) {
138
+ if (explicit.has(key)) continue;
139
+ const v = get();
140
+ if (v == null || v === "") continue;
141
+ headers[name] = v;
142
+ }
143
+ if (Object.keys(headers).length > 0) {
123
144
  parts.headers = headers;
124
145
  }
125
146
  if (this.#body !== void 0) {
@@ -74,8 +74,106 @@ function checkOptionalAccessNode(chk, ast, ctx) {
74
74
  const actualType = leftType.kind === "optional" ? leftType.valueType : leftType;
75
75
  return chk.registry.getOptionalType(chk.checkAccessOnType(ast, ctx, actualType, true));
76
76
  }
77
- function checkElement(chk, ctx, expected, el) {
78
- return expected.unify(chk.registry, chk.check(el, ctx)) ?? dynType;
77
+ function spreadInner(node) {
78
+ return node.args;
79
+ }
80
+ function spreadListElementType(chk, ctx, node) {
81
+ const t = chk.check(spreadInner(node), ctx);
82
+ if (t.kind === "dyn") return chk.dynType;
83
+ if (t.kind === "list") return t.valueType;
84
+ throw chk.createError(
85
+ "invalid_spread",
86
+ `Cannot spread '${chk.formatType(t)}' into a list (expected a list)`,
87
+ node
88
+ );
89
+ }
90
+ function spreadMapEntryTypes(chk, ctx, node) {
91
+ const t = chk.check(spreadInner(node), ctx);
92
+ if (t.kind === "dyn") return [chk.dynType, chk.dynType];
93
+ if (t.kind === "map") return [t.keyType, t.valueType];
94
+ if (t.kind === "message") {
95
+ const Base2 = chk;
96
+ return [Base2.stringType, chk.dynType];
97
+ }
98
+ throw chk.createError(
99
+ "invalid_spread",
100
+ `Cannot spread '${chk.formatType(t)}' into a map (expected a map)`,
101
+ node
102
+ );
103
+ }
104
+ const SPREAD_SKIP_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
105
+ function assignFragmentEntry(obj, k, v) {
106
+ const key = k;
107
+ if (SPREAD_SKIP_KEYS.has(key)) return;
108
+ obj[key] = v;
109
+ }
110
+ function mergeMapFragments(ev, frags) {
111
+ const obj = {};
112
+ for (const f of frags) {
113
+ if ("kv" in f) {
114
+ assignFragmentEntry(obj, f.kv[0], f.kv[1]);
115
+ continue;
116
+ }
117
+ const src = f.spread;
118
+ if (src instanceof Map) {
119
+ for (const [k, v] of src) assignFragmentEntry(obj, k, v);
120
+ continue;
121
+ }
122
+ if (src !== null && typeof src === "object" && !isArray(src)) {
123
+ for (const k in src) {
124
+ if (hasOwn(src, k)) assignFragmentEntry(obj, k, src[k]);
125
+ }
126
+ continue;
127
+ }
128
+ throw ev.createError(
129
+ "invalid_spread",
130
+ `Cannot spread a non-map value into a map`,
131
+ f.node
132
+ );
133
+ }
134
+ return obj;
135
+ }
136
+ function evaluateSpreadList(ev, ast, ctx) {
137
+ const arr = ast.args;
138
+ return Effect.all(
139
+ arr.map(
140
+ (el) => el.op === "spread" ? ev.eval(spreadInner(el), ctx).pipe(
141
+ Effect.flatMap(
142
+ (v) => isArray(v) ? Effect.succeed(v) : Effect.fail(
143
+ ev.createError(
144
+ "invalid_spread",
145
+ `Cannot spread a non-list value into a list`,
146
+ el
147
+ )
148
+ )
149
+ )
150
+ ) : ev.eval(el, ctx).pipe(Effect.map((v) => [v]))
151
+ )
152
+ ).pipe(Effect.map((parts) => parts.flat()));
153
+ }
154
+ function evaluateSpreadMap(ev, ast, ctx) {
155
+ const arr = ast.args;
156
+ return Effect.all(
157
+ arr.map((e) => {
158
+ if (e.length === 1 || e[0].op === "spread") {
159
+ const node = e[0];
160
+ return ev.eval(spreadInner(node), ctx).pipe(
161
+ Effect.map((src) => ({ spread: src, node }))
162
+ );
163
+ }
164
+ const pair = e;
165
+ return Effect.all([ev.eval(pair[0], ctx), ev.eval(pair[1], ctx)]).pipe(
166
+ Effect.map(([k, v]) => ({ kv: [k, v] }))
167
+ );
168
+ })
169
+ ).pipe(
170
+ Effect.flatMap(
171
+ (frags) => Effect.try({
172
+ try: () => mergeMapFragments(ev, frags),
173
+ catch: (e) => e
174
+ })
175
+ )
176
+ );
79
177
  }
80
178
  function ternaryConditionError(ev, value, node) {
81
179
  const type = ev.debugRuntimeType(value);
@@ -481,9 +579,19 @@ const OPERATORS_MAP = {
481
579
  const arr = ast.args;
482
580
  const arrLen = arr.length;
483
581
  if (arrLen === 0) return ast.setMeta("evaluate", emptyList) && chk.getType("list<T>");
484
- let valueType = chk.check(arr[0], ctx);
485
- for (let i = 1; i < arrLen; i++)
486
- valueType = checkElement(chk, ctx, valueType, arr[i]);
582
+ let hasSpread = false;
583
+ let valueType;
584
+ for (const el of arr) {
585
+ let t;
586
+ if (el.op === "spread") {
587
+ hasSpread = true;
588
+ t = spreadListElementType(chk, ctx, el);
589
+ } else {
590
+ t = chk.check(el, ctx);
591
+ }
592
+ valueType = valueType === void 0 ? t : valueType.unify(chk.registry, t) ?? dynType;
593
+ }
594
+ if (hasSpread) ast.setMeta("evaluate", evaluateSpreadList);
487
595
  return chk.registry.getListType(valueType);
488
596
  },
489
597
  evaluate(ev, ast, ctx) {
@@ -495,13 +603,23 @@ const OPERATORS_MAP = {
495
603
  const arr = ast.args;
496
604
  const arrLen = arr.length;
497
605
  if (arrLen === 0) return ast.setMeta("evaluate", emptyMap) && chk.getType("map<K, V>");
498
- let keyType = chk.check(arr[0][0], ctx);
499
- let valueType = chk.check(arr[0][1], ctx);
500
- for (let i = 1; i < arrLen; i++) {
501
- const e = arr[i];
502
- keyType = checkElement(chk, ctx, keyType, e[0]);
503
- valueType = checkElement(chk, ctx, valueType, e[1]);
606
+ let hasSpread = false;
607
+ let keyType;
608
+ let valueType;
609
+ for (const e of arr) {
610
+ let k;
611
+ let v;
612
+ if (e.length === 1 || e[0].op === "spread") {
613
+ hasSpread = true;
614
+ [k, v] = spreadMapEntryTypes(chk, ctx, e[0]);
615
+ } else {
616
+ k = chk.check(e[0], ctx);
617
+ v = chk.check(e[1], ctx);
618
+ }
619
+ keyType = keyType === void 0 ? k : keyType.unify(chk.registry, k) ?? dynType;
620
+ valueType = valueType === void 0 ? v : valueType.unify(chk.registry, v) ?? dynType;
504
621
  }
622
+ if (hasSpread) ast.setMeta("evaluate", evaluateSpreadMap);
505
623
  return chk.registry.getMapType(keyType, valueType);
506
624
  },
507
625
  evaluate(ev, ast, ctx) {
@@ -513,6 +631,16 @@ const OPERATORS_MAP = {
513
631
  ).pipe(Effect.map(safeFromEntries));
514
632
  }
515
633
  },
634
+ "spread": {
635
+ // Reached only via direct `ast.check`/`ast.evaluate` — parents consume the
636
+ // node inline, so these are defensive guards.
637
+ check(chk, ast) {
638
+ throw chk.createError("misplaced_spread", `'...' is only valid inside a list or map literal`, ast);
639
+ },
640
+ evaluate(ev, ast) {
641
+ return Effect.fail(ev.createError("misplaced_spread", `'...' is only valid inside a list or map literal`, ast));
642
+ }
643
+ },
516
644
  "comprehension": {
517
645
  check(chk, ast, ctx) {
518
646
  const args = ast.args;
@@ -76,8 +76,9 @@ export declare class Parser {
76
76
  parsePostfix(): ASTNode;
77
77
  parsePrimary(): ASTNode;
78
78
  parseList(): ASTNode;
79
+ parseListElement(): ASTNode;
79
80
  parseMap(): ASTNode;
80
- parseProperty(): [ASTNode, ASTNode];
81
+ parseProperty(): [ASTNode, ASTNode] | [ASTNode];
81
82
  parseArgumentList(): ASTNode[];
82
83
  }
83
84
  export {};
@@ -34,7 +34,8 @@ const TOKEN = {
34
34
  COMMA: 28,
35
35
  COLON: 29,
36
36
  QUESTION: 30,
37
- BYTES: 31
37
+ BYTES: 31,
38
+ ELLIPSIS: 32
38
39
  };
39
40
  const OP_FOR_TOKEN = {
40
41
  [TOKEN.EQ]: OPS["=="],
@@ -208,6 +209,8 @@ class Lexer {
208
209
  case "}":
209
210
  return this.token(this.pos++, TOKEN.RBRACE);
210
211
  case ".":
212
+ if (input[pos + 1] === "." && input[pos + 2] === ".")
213
+ return this.token((this.pos += 3) - 3, TOKEN.ELLIPSIS);
211
214
  return this.token(this.pos++, TOKEN.DOT);
212
215
  case ",":
213
216
  return this.token(this.pos++, TOKEN.COMMA);
@@ -735,12 +738,12 @@ export class Parser {
735
738
  const elements = [];
736
739
  let remainingElements = this.limits.maxListElements;
737
740
  if (!this.match(TOKEN.RBRACKET)) {
738
- elements.push(this.parseExpression());
741
+ elements.push(this.parseListElement());
739
742
  if (!remainingElements--) this.#limitExceeded("maxListElements", elements.at(-1).pos);
740
743
  while (this.match(TOKEN.COMMA)) {
741
744
  this.#advanceToken();
742
745
  if (this.match(TOKEN.RBRACKET)) break;
743
- elements.push(this.parseExpression());
746
+ elements.push(this.parseListElement());
744
747
  if (!remainingElements--) this.#limitExceeded("maxListElements", elements.at(-1).pos);
745
748
  }
746
749
  }
@@ -748,6 +751,10 @@ export class Parser {
748
751
  this.consume(TOKEN.RBRACKET);
749
752
  return this.#node(start, closeEnd, OPS.list, elements);
750
753
  }
754
+ parseListElement() {
755
+ if (this.match(TOKEN.ELLIPSIS)) return this.#parseSpread();
756
+ return this.parseExpression();
757
+ }
751
758
  parseMap() {
752
759
  const start = this.consume(TOKEN.LBRACE);
753
760
  const props = [];
@@ -767,8 +774,18 @@ export class Parser {
767
774
  return this.#node(start, closeEnd, OPS.map, props);
768
775
  }
769
776
  parseProperty() {
777
+ if (this.match(TOKEN.ELLIPSIS)) return [this.#parseSpread()];
770
778
  return [this.parseExpression(), (this.consume(TOKEN.COLON), this.parseExpression())];
771
779
  }
780
+ // `...expr` — only valid as a list element or map entry; the parent literal
781
+ // consumes the node and `parsePrimary` never sees ELLIPSIS, so a stray `...`
782
+ // surfaces as a normal "unexpected token" parse error.
783
+ #parseSpread() {
784
+ const start = this.pos;
785
+ this.#advanceToken();
786
+ const inner = this.parseExpression();
787
+ return this.#node(start, inner.end, OPS.spread, inner);
788
+ }
772
789
  parseArgumentList() {
773
790
  const args = [];
774
791
  let remainingArgs = this.limits.maxCallArguments;
@@ -79,7 +79,11 @@ export function serialize(ast) {
79
79
  case "list":
80
80
  return `[${args.map(serialize).join(", ")}]`;
81
81
  case "map":
82
- return `{${args.map(([k, v]) => `${serialize(k)}: ${serialize(v)}`).join(", ")}}`;
82
+ return `{${args.map(
83
+ (e) => e.length === 1 || e[0].op === "spread" ? serialize(e[0]) : `${serialize(e[0])}: ${serialize(e[1])}`
84
+ ).join(", ")}}`;
85
+ case "spread":
86
+ return `...${serialize(args)}`;
83
87
  case "?:": {
84
88
  const ternArgs = args;
85
89
  return `${wrap(ternArgs[0], op)} ? ${wrap(ternArgs[1], op)} : ${serialize(ternArgs[2])}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/config",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {