@shwfed/config 2.2.3 → 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.3",
4
+ "version": "2.2.4",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -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.3",
3
+ "version": "2.2.4",
4
4
  "description": "Configurable UI for SHWFED",
5
5
  "type": "module",
6
6
  "publishConfig": {