@shwfed/config 2.2.2 → 2.2.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shwfed",
3
3
  "configKey": "shwfed",
4
- "version": "2.2.2",
4
+ "version": "2.2.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -80,10 +80,13 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
80
80
  }>;
81
81
  }>>>;
82
82
  kind: Schema.tag<"shwfed.component.form">;
83
- initial: Schema.optional<Schema.Struct<{
83
+ initial: Schema.optional<Schema.transform<Schema.Union<[Schema.Schema<string, string, never>, Schema.Struct<{
84
84
  request: Schema.optional<Schema.Schema<string, string, never>>;
85
85
  data: Schema.Schema<string, string, never>;
86
- }>>;
86
+ }>]>, Schema.Struct<{
87
+ request: Schema.optional<Schema.Schema<string, string, never>>;
88
+ data: Schema.Schema<string, string, never>;
89
+ }>>>;
87
90
  readonly: Schema.optional<Schema.Schema<string, string, never>>;
88
91
  }>>;
89
92
  }>;
@@ -347,10 +347,13 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
347
347
  }>;
348
348
  }>>>;
349
349
  kind: Schema.tag<"shwfed.component.form">;
350
- initial: Schema.optional<Schema.Struct<{
350
+ initial: Schema.optional<Schema.transform<Schema.Union<[Schema.Schema<string, string, never>, Schema.Struct<{
351
351
  request: Schema.optional<Schema.Schema<string, string, never>>;
352
352
  data: Schema.Schema<string, string, never>;
353
- }>>;
353
+ }>]>, Schema.Struct<{
354
+ request: Schema.optional<Schema.Schema<string, string, never>>;
355
+ data: Schema.Schema<string, string, never>;
356
+ }>>>;
354
357
  readonly: Schema.optional<Schema.Schema<string, string, never>>;
355
358
  }>>>;
356
359
  cellStyle: Schema.optional<Schema.Schema<string, string, never>>;
@@ -138,10 +138,13 @@ export declare function FormConfig(configure: (env: Environment) => void): Schem
138
138
  }>;
139
139
  }>>>;
140
140
  kind: Schema.tag<"shwfed.component.form">;
141
- initial: Schema.optional<Schema.Struct<{
141
+ initial: Schema.optional<Schema.transform<Schema.Union<[Schema.Schema<string, string, never>, Schema.Struct<{
142
142
  request: Schema.optional<Schema.Schema<string, string, never>>;
143
143
  data: Schema.Schema<string, string, never>;
144
- }>>;
144
+ }>]>, Schema.Struct<{
145
+ request: Schema.optional<Schema.Schema<string, string, never>>;
146
+ data: Schema.Schema<string, string, never>;
147
+ }>>>;
145
148
  readonly: Schema.optional<Schema.Schema<string, string, never>>;
146
149
  }>>;
147
150
  export declare function createFormConfig(body: Omit<Schema.Schema.Type<ReturnType<typeof FormConfig>>, 'kind'>): {
@@ -58,30 +58,40 @@ export function FormConfig(configure) {
58
58
  resultType: "dyn"
59
59
  });
60
60
  const CelFormReadonly = Expression({ configure: formConfigure, resultType: "bool" });
61
- return Schema.Struct({
62
- kind: Schema.tag(KIND),
63
- initial: Schema.optional(Schema.Struct({
64
- request: Schema.optional(CelInitialRequest.annotations({
65
- title: "\u8BF7\u6C42",
66
- description: md`
67
- 可选的 HTTP 请求表达式:返回 \`HttpRequest\`,运行时由宿主发起,并把响应体作为 \`json\` 传给「数据」表达式。
61
+ const InitialStruct = Schema.Struct({
62
+ request: Schema.optional(CelInitialRequest.annotations({
63
+ title: "\u8BF7\u6C42",
64
+ description: md`
65
+ 可选的 HTTP 请求表达式:返回 \`HttpRequest\`,运行时由宿主发起,并把响应体作为 \`json\` 传给「数据」表达式。
68
66
 
69
- 留空时「数据」直接对 CEL 上下文求值(静态默认值,或引用 \`form\` 的表达式)。
70
- `
71
- })),
72
- data: CelInitialData.annotations({
73
- title: "\u6570\u636E",
74
- description: md`
75
- 返回整个表单初始值的 CEL 表达式(应为一个对象):
67
+ 留空时「数据」直接对 CEL 上下文求值(静态默认值,或引用 \`form\` 的表达式)。
68
+ `
69
+ })),
70
+ data: CelInitialData.annotations({
71
+ title: "\u6570\u636E",
72
+ description: md`
73
+ 返回整个表单初始值的 CEL 表达式(应为一个对象):
76
74
 
77
- - 可以互相依赖,但应避免循环引用。考虑一个含「用户名」与「用户角色」的表单:可默认填入当前登入人的用户名,再以此为依据填入其默认角色。
75
+ - 可以互相依赖,但应避免循环引用。考虑一个含「用户名」与「用户角色」的表单:可默认填入当前登入人的用户名,再以此为依据填入其默认角色。
78
76
 
79
- - 配置了「请求」时,可通过 \`json\` 引用响应体,例如 \`json.?data\`。
77
+ - 配置了「请求」时,可通过 \`json\` 引用响应体,例如 \`json.?data\`。
80
78
 
81
- - 如果配置了初始值,重置这个表单将**重置为其初始值,而非空**。
82
- `
83
- })
84
- }).annotations({
79
+ - 如果配置了初始值,重置这个表单将**重置为其初始值,而非空**。
80
+ `
81
+ })
82
+ });
83
+ const Initial = Schema.transform(
84
+ Schema.Union(CelInitialData, InitialStruct),
85
+ InitialStruct,
86
+ {
87
+ strict: true,
88
+ decode: (input) => typeof input === "string" ? { data: input } : input,
89
+ encode: (output) => output
90
+ }
91
+ );
92
+ return Schema.Struct({
93
+ kind: Schema.tag(KIND),
94
+ initial: Schema.optional(Initial.annotations({
85
95
  title: "\u521D\u59CB\u503C",
86
96
  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"
87
97
  })),
@@ -432,10 +432,13 @@ export declare function TableConfig(configure: (env: Environment) => void): Sche
432
432
  }>;
433
433
  }>>>;
434
434
  kind: Schema.tag<"shwfed.component.form">;
435
- initial: Schema.optional<Schema.Struct<{
435
+ initial: Schema.optional<Schema.transform<Schema.Union<[Schema.Schema<string, string, never>, Schema.Struct<{
436
436
  request: Schema.optional<Schema.Schema<string, string, never>>;
437
437
  data: Schema.Schema<string, string, never>;
438
- }>>;
438
+ }>]>, Schema.Struct<{
439
+ request: Schema.optional<Schema.Schema<string, string, never>>;
440
+ data: Schema.Schema<string, string, never>;
441
+ }>>>;
439
442
  readonly: Schema.optional<Schema.Schema<string, string, never>>;
440
443
  }>>>;
441
444
  cellStyle: Schema.optional<Schema.Schema<string, string, never>>;
@@ -214,7 +214,6 @@ function applyShortcut(index) {
214
214
  <PopoverContent
215
215
  align="start"
216
216
  :class="cn('w-auto p-0', props.popoverClass)"
217
- :style="{ minWidth: 'var(--reka-popover-trigger-width)' }"
218
217
  @open-auto-focus="(event) => event.preventDefault()"
219
218
  @interact-outside="onInteractOutside"
220
219
  >
@@ -22,6 +22,8 @@ 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 `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
+
25
27
  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.
26
28
 
27
29
  ## Architecture
@@ -39,6 +41,7 @@ lib/
39
41
  ├── macros.ts — map, filter, all, exists, exists_one, find, has, cel.bind
40
42
  ├── http-builtins.ts — `http` constant + http/HttpRequest types (local addition)
41
43
  ├── http-builder.ts — `HttpRequestBuilder` — what an http.* expression returns
44
+ ├── json-builtins.ts — `JSON` constant + `JSON.parse` / `JSON.stringify` (local addition)
42
45
  ├── decimal.ts — Decimal class (arbitrary precision)
43
46
  ├── optional.ts — Optional type support (uses Effect Option)
44
47
  ├── serialize.ts — AST back to CEL string
@@ -160,6 +160,20 @@ b"hello".at(0) // 104
160
160
  size(b"hello") // 5
161
161
  ```
162
162
 
163
+ ### JSON
164
+ ```cel
165
+ JSON.parse('{"a":1,"b":[2,3]}') // dyn value: {"a": 1, "b": [2, 3]}
166
+ JSON.parse("42") // 42
167
+ JSON.stringify({"a": 1, "b": [2,3]}) // '{"a":1,"b":[2,3]}'
168
+ JSON.stringify(1 + 2) // "3"
169
+ ```
170
+ `JSON` is a built-in namespace — you don't import it, it's always available.
171
+ `JSON.parse` accepts any JSON string and returns a `dyn` value.
172
+ `JSON.stringify` takes any value and serializes it; CEL-internal shapes are
173
+ unwrapped first so the output is plain JSON — numbers (CEL's `Decimal`) become
174
+ JS numbers, optionals become their value (or `null` when empty), and `Date`
175
+ values become ISO 8601 strings.
176
+
163
177
  ### Dates
164
178
  ```cel
165
179
  date("2023-06-15") // create a Date
@@ -3,6 +3,7 @@ import { createRegistry, RootContext } from "./registry.js";
3
3
  import { evaluationError } from "./errors.js";
4
4
  import { registerFunctions } from "./functions.js";
5
5
  import { registerHttpBuiltins } from "./http-builtins.js";
6
+ import { registerJSONBuiltins } from "./json-builtins.js";
6
7
  import { registerMacros } from "./macros.js";
7
8
  import { registerOverloads } from "./overloads.js";
8
9
  import { TypeChecker } from "./type-checker.js";
@@ -14,6 +15,7 @@ registerFunctions(globalRegistry);
14
15
  registerOverloads(globalRegistry);
15
16
  registerMacros(globalRegistry);
16
17
  registerHttpBuiltins(globalRegistry);
18
+ registerJSONBuiltins(globalRegistry);
17
19
  class Environment {
18
20
  #registry;
19
21
  #evaluator;
@@ -0,0 +1,2 @@
1
+ import type { Registry } from './registry.js.js';
2
+ export declare function registerJSONBuiltins(registry: Registry): void;
@@ -0,0 +1,51 @@
1
+ import { Option } from "effect";
2
+ import { evaluationError } from "./errors.js";
3
+ import { Decimal } from "./decimal.js";
4
+ class JSONNamespace {
5
+ brand = "JSON";
6
+ }
7
+ const JSON_INSTANCE = new JSONNamespace();
8
+ function normalizeForJSON(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) ? normalizeForJSON(value.value) : null;
13
+ }
14
+ if (value instanceof Date) return value;
15
+ if (value instanceof Map) {
16
+ const out = {};
17
+ for (const [k, v] of value) out[String(k)] = normalizeForJSON(v);
18
+ return out;
19
+ }
20
+ if (Array.isArray(value)) return value.map(normalizeForJSON);
21
+ if (typeof value === "object") {
22
+ const proto = Object.getPrototypeOf(value);
23
+ if (proto !== Object.prototype && proto !== null) return value;
24
+ const out = {};
25
+ for (const key of Object.keys(value)) {
26
+ out[key] = normalizeForJSON(value[key]);
27
+ }
28
+ return out;
29
+ }
30
+ return value;
31
+ }
32
+ export function registerJSONBuiltins(registry) {
33
+ registry.registerType("JSON", JSONNamespace);
34
+ registry.registerVariable({
35
+ name: "JSON",
36
+ type: "JSON",
37
+ value: JSON_INSTANCE,
38
+ description: "JSON \u7F16\u89E3\u7801"
39
+ });
40
+ registry.registerFunctionOverload("JSON.parse(string): dyn", (_r, s) => {
41
+ try {
42
+ return globalThis.JSON.parse(s);
43
+ } catch (e) {
44
+ throw evaluationError("invalid_json", `JSON.parse(): ${e.message}`);
45
+ }
46
+ });
47
+ registry.registerFunctionOverload(
48
+ "JSON.stringify(dyn): string",
49
+ (_r, v) => globalThis.JSON.stringify(normalizeForJSON(v))
50
+ );
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/config",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {