@formbar/arbiter 0.8.0 → 0.9.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/README.md CHANGED
@@ -95,6 +95,51 @@ form.dispose();
95
95
  Pass rules through `createArbiterPlugin` in the `plugins` option. The former top-level `arbiterRules` form option is
96
96
  not supported; non-production core builds warn with this migration path when they encounter it.
97
97
 
98
+ ## Field policy output
99
+
100
+ Rules can contribute normalized field policy through ordinary `$set` and `$unset` stages under the reserved
101
+ `$formbar.fieldPolicy.<outputId>` root:
102
+
103
+ ```ts
104
+ const rules = [
105
+ {
106
+ name: "regional-policy",
107
+ when: { country: "US" },
108
+ then: [
109
+ {
110
+ $set: {
111
+ "$formbar.fieldPolicy.state": {
112
+ path: "/address/state",
113
+ visible: true,
114
+ disabled: false,
115
+ readOnly: false,
116
+ required: true,
117
+ label: "State",
118
+ },
119
+ },
120
+ },
121
+ ],
122
+ },
123
+ ];
124
+ ```
125
+
126
+ Output IDs begin with a letter and contain only letters, digits, `_`, or `-`. Each output is one strict record with
127
+ an absolute RFC-6901 data pointer and at least one of `visible`, `disabled`, `readOnly`, `required`, or `label`.
128
+ Boolean values are strict; labels are strings and may be empty. IDs are applied in lexical order, and every relevant
129
+ evaluation replaces Arbiter's complete policy contribution. Removing a rule or output therefore removes its policy on
130
+ the next Formbar evaluation. Evaluations skipped because no data or UI changed leave the previous contribution intact.
131
+
132
+ Nested paths, literal dotted keys (`/profile.name`), concrete array indices (`/items/0/code`), and literal data rooted
133
+ at `/$ui/...` are supported. Relative paths, UI-namespace paths such as `$ui.name`, wildcards, lexical scope
134
+ placeholders, and dynamic repeater targets are not supported. Multiple output IDs may not normalize to the same path.
135
+ Core owns path normalization and cross-producer policy replacement; declarative runtimes own restrictive resolution.
136
+
137
+ `$formbar` is an internal Arbiter channel, not a synchronized form-data root. A top-level `$formbar` key, including a
138
+ literal dotted key beginning with `$formbar.`, is ignored by this adapter, so payload data cannot create or override
139
+ policy output. Rule-authored output remains internal and is never written into Formbar data or UI state. Output records
140
+ must be plain enumerable data properties; accessors are rejected without invoking getters, and validated records are
141
+ copied into fresh frozen values.
142
+
98
143
  ## When to use this package
99
144
 
100
145
  - Use `@formbar/arbiter` when visibility, requiredness, computed values, or other form behavior should be governed by Arbitre production rules.
package/dist/index.cjs CHANGED
@@ -1,10 +1,133 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@arbitre/core');
4
+ var core$1 = require('@formbar/core');
4
5
  var kuery = require('kuery');
5
6
  var expressions = require('@formbar/expressions');
6
7
 
7
8
  // src/arbiter-plugin.ts
9
+ var OUTPUT_ROOT = "$formbar.fieldPolicy";
10
+ var OUTPUT_ID = /^[A-Za-z][A-Za-z0-9_-]*$/;
11
+ var POLICY_KEYS = /* @__PURE__ */ new Set(["path", "visible", "disabled", "readOnly", "required", "label"]);
12
+ var BOOLEAN_KEYS = ["visible", "disabled", "readOnly", "required"];
13
+ var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
14
+ var INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
15
+ function outputError(message, outputId) {
16
+ throw new core.ArbiterError(core.ArbiterErrorCode.RULE_COMPILATION_FAILED, message, {
17
+ details: outputId === void 0 ? { root: OUTPUT_ROOT } : { root: OUTPUT_ROOT, outputId }
18
+ });
19
+ }
20
+ function pathError(message, outputId, path) {
21
+ throw new core.ArbiterError(core.ArbiterErrorCode.INVALID_PATH, message, {
22
+ details: { root: OUTPUT_ROOT, outputId, path }
23
+ });
24
+ }
25
+ function captureRecord(value, outputId) {
26
+ if (value === null || typeof value !== "object") {
27
+ return outputError("Arbiter field policy output must be a plain data record", outputId);
28
+ }
29
+ let array;
30
+ let prototype;
31
+ let keys;
32
+ const descriptors = /* @__PURE__ */ new Map();
33
+ try {
34
+ array = Array.isArray(value);
35
+ prototype = Reflect.getPrototypeOf(value);
36
+ keys = Reflect.ownKeys(value);
37
+ for (const key of keys) descriptors.set(key, Reflect.getOwnPropertyDescriptor(value, key));
38
+ } catch {
39
+ return outputError("Arbiter field policy output must be inspectable", outputId);
40
+ }
41
+ if (array) return outputError("Arbiter field policy output must be a plain data record", outputId);
42
+ if (prototype !== Object.prototype && prototype !== null) {
43
+ return outputError("Arbiter field policy output must use a plain prototype", outputId);
44
+ }
45
+ const values = /* @__PURE__ */ new Map();
46
+ for (const key of keys) {
47
+ const descriptor = descriptors.get(key);
48
+ if (typeof key !== "string" || !descriptor?.enumerable || !("value" in descriptor)) {
49
+ return outputError("Arbiter field policy output requires enumerable data properties", outputId);
50
+ }
51
+ values.set(key, descriptor.value);
52
+ }
53
+ return { keys: [...values.keys()], values };
54
+ }
55
+ function normalizedPathKey(path, outputId) {
56
+ if (typeof path !== "string" || !path.startsWith("/") || path === "/") {
57
+ return pathError(`Field policy output "${outputId}" path must be a non-empty RFC-6901 pointer`, outputId, path);
58
+ }
59
+ let segments;
60
+ try {
61
+ segments = core$1.parsePath(path).segments;
62
+ } catch {
63
+ return pathError(`Field policy output "${outputId}" has an invalid RFC-6901 pointer`, outputId, path);
64
+ }
65
+ if (segments.some((segment) => segment === "" || segment === "*" || UNSAFE_SEGMENTS.has(String(segment)))) {
66
+ return pathError(`Field policy output "${outputId}" has an unsupported target path`, outputId, path);
67
+ }
68
+ const normalized = segments.map((segment) => INTEGER_SEGMENT.test(String(segment)) ? Number(segment) : segment);
69
+ if (normalized.some((segment) => typeof segment === "number" && !Number.isSafeInteger(segment))) {
70
+ return pathError(`Field policy output "${outputId}" has an unsafe array index`, outputId, path);
71
+ }
72
+ return JSON.stringify(normalized);
73
+ }
74
+ function decodeRecord(value, outputId) {
75
+ const { keys, values } = captureRecord(value, outputId);
76
+ for (const key of keys) {
77
+ if (!POLICY_KEYS.has(key)) outputError(`Field policy output "${outputId}" has unknown property "${key}"`, outputId);
78
+ }
79
+ if (!keys.includes("path")) outputError(`Field policy output "${outputId}" requires "path"`, outputId);
80
+ if (!keys.some((key) => key !== "path")) {
81
+ outputError(`Field policy output "${outputId}" requires at least one policy property`, outputId);
82
+ }
83
+ for (const key of BOOLEAN_KEYS) {
84
+ if (values.has(key) && typeof values.get(key) !== "boolean") {
85
+ outputError(`Field policy output "${outputId}" property "${key}" must be boolean`, outputId);
86
+ }
87
+ }
88
+ if (values.has("label") && typeof values.get("label") !== "string") {
89
+ outputError(`Field policy output "${outputId}" property "label" must be a string`, outputId);
90
+ }
91
+ const path = values.get("path");
92
+ const pathKey = normalizedPathKey(path, outputId);
93
+ const input = Object.freeze({
94
+ path,
95
+ ...values.has("visible") ? { visible: values.get("visible") } : {},
96
+ ...values.has("disabled") ? { disabled: values.get("disabled") } : {},
97
+ ...values.has("readOnly") ? { readOnly: values.get("readOnly") } : {},
98
+ ...values.has("required") ? { required: values.get("required") } : {},
99
+ ...values.has("label") ? { label: values.get("label") } : {}
100
+ });
101
+ return { input, pathKey };
102
+ }
103
+ function readFieldPolicyOutput(session) {
104
+ let root;
105
+ try {
106
+ root = session.getPath(OUTPUT_ROOT);
107
+ } catch (error) {
108
+ if (error instanceof core.ArbiterError) throw error;
109
+ return outputError("Arbiter field policy output root could not be read");
110
+ }
111
+ if (root === void 0) return Object.freeze([]);
112
+ const captured = captureRecord(root);
113
+ const outputIds = [...captured.keys].sort();
114
+ const seen = /* @__PURE__ */ new Map();
115
+ const inputs = outputIds.map((outputId) => {
116
+ if (!OUTPUT_ID.test(outputId)) outputError(`Invalid field policy output ID "${outputId}"`, outputId);
117
+ const decoded = decodeRecord(captured.values.get(outputId), outputId);
118
+ const duplicate = seen.get(decoded.pathKey);
119
+ if (duplicate) {
120
+ pathError(
121
+ `Field policy outputs "${duplicate}" and "${outputId}" target the same path`,
122
+ outputId,
123
+ decoded.input.path
124
+ );
125
+ }
126
+ seen.set(decoded.pathKey, outputId);
127
+ return decoded.input;
128
+ });
129
+ return Object.freeze(inputs);
130
+ }
8
131
 
9
132
  // src/internal-paths.ts
10
133
  function isArbiterInternalPath(path) {
@@ -12,32 +135,55 @@ function isArbiterInternalPath(path) {
12
135
  }
13
136
 
14
137
  // src/arbiter-plugin.ts
138
+ var RESERVED_DATA_ROOT = "$formbar";
15
139
  function resolveSession(options) {
16
140
  if (options.session) return { session: options.session, owned: false };
17
141
  if (options.rules) return { session: core.createSession({ rules: options.rules }), owned: true };
18
142
  throw new Error("createArbiterPlugin requires either `rules` or `session`");
19
143
  }
20
- function syncSession(session, ctx) {
21
- const data = ctx.data;
22
- for (const key of Object.keys(data)) session.assert(key, data[key]);
23
- const uiState = ctx.uiState;
24
- for (const key of Object.keys(uiState)) session.assert(`$ui.${key}`, uiState[key]);
144
+ function syncRoots(session, values, previous, prefix, include = () => true) {
145
+ const current = new Set(Object.keys(values).filter(include));
146
+ for (const key of previous) {
147
+ if (!current.has(key)) session.retract(`${prefix}${key}`);
148
+ }
149
+ for (const key of current) session.assert(`${prefix}${key}`, values[key]);
150
+ return current;
151
+ }
152
+ function isFormDataRoot(key) {
153
+ return key !== RESERVED_DATA_ROOT && !key.startsWith(`${RESERVED_DATA_ROOT}.`);
154
+ }
155
+ function syncSession(session, ctx, roots) {
156
+ roots.data = syncRoots(session, ctx.data, roots.data, "", isFormDataRoot);
157
+ roots.ui = syncRoots(session, ctx.uiState, roots.ui, "$ui.");
158
+ }
159
+ function fireSession(session) {
160
+ try {
161
+ return session.fire();
162
+ } catch (error) {
163
+ if (error instanceof core.ArbiterError) throw error;
164
+ throw new core.ArbiterError(
165
+ core.ArbiterErrorCode.RULE_COMPILATION_FAILED,
166
+ "Arbiter session state could not be evaluated safely",
167
+ error instanceof Error ? { details: { root: "$formbar.fieldPolicy" }, cause: error } : { details: { root: "$formbar.fieldPolicy" } }
168
+ );
169
+ }
25
170
  }
26
171
  function toWrites(result) {
27
172
  return result.changes.filter((change) => !isArbiterInternalPath(change.path)).map((change) => ({ path: change.path, value: change.newValue, mode: "set" }));
28
173
  }
29
- function evaluateSession(session, ctx) {
174
+ function evaluateSession(session, ctx, roots) {
30
175
  if (ctx.origin.startsWith("plugin:arbiter")) return;
31
176
  if (!ctx.change.dataChanged && !ctx.change.uiChanged) return;
32
- syncSession(session, ctx);
33
- const writes = toWrites(session.fire());
34
- return { writes: writes.length > 0 ? writes : void 0 };
177
+ syncSession(session, ctx, roots);
178
+ const writes = toWrites(fireSession(session));
179
+ return { writes: writes.length > 0 ? writes : void 0, fieldPolicy: readFieldPolicyOutput(session) };
35
180
  }
36
181
  function createArbiterPlugin(options) {
37
182
  const { session, owned } = resolveSession(options);
183
+ const roots = { data: /* @__PURE__ */ new Set(), ui: /* @__PURE__ */ new Set() };
38
184
  return {
39
185
  id: "arbiter",
40
- evaluate: (ctx) => evaluateSession(session, ctx),
186
+ evaluate: (ctx) => evaluateSession(session, ctx, roots),
41
187
  onDispose() {
42
188
  if (owned) session.dispose();
43
189
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal-paths.ts","../src/arbiter-plugin.ts","../src/expression-utils.ts","../src/expression-then-operator.ts"],"names":["createSession","evaluate","synchronousValue","ExpressionError","createExpressionService"],"mappings":";;;;;;;;;AAIO,SAAS,sBAAsB,IAAA,EAAuB;AAC5D,EAAA,OAAO,KAAK,UAAA,CAAW,GAAG,KAAK,CAAC,IAAA,CAAK,WAAW,MAAM,CAAA;AACvD;;;ACMA,SAAS,eAAe,OAAA,EAAyE;AAChG,EAAA,IAAI,OAAA,CAAQ,SAAS,OAAO,EAAE,SAAS,OAAA,CAAQ,OAAA,EAAS,OAAO,KAAA,EAAM;AACrE,EAAA,IAAI,OAAA,CAAQ,KAAA,EAAO,OAAO,EAAE,OAAA,EAASA,kBAAA,CAAc,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAA2B,CAAA,EAAG,OAAO,IAAA,EAAK;AAC9G,EAAA,MAAM,IAAI,MAAM,0DAA0D,CAAA;AAC3E;AAEA,SAAS,WAAA,CAAY,SAAsB,GAAA,EAAkC;AAC5E,EAAA,MAAM,OAAO,GAAA,CAAI,IAAA;AACjB,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,UAAW,MAAA,CAAO,GAAA,EAAK,IAAA,CAAK,GAAG,CAAC,CAAA;AAClE,EAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,EAAG,OAAA,CAAQ,MAAA,CAAO,CAAA,IAAA,EAAO,GAAG,CAAA,CAAA,EAAI,OAAA,CAAQ,GAAG,CAAC,CAAA;AAClF;AAEA,SAAS,SAAS,MAAA,EAA8C;AAC/D,EAAA,OAAO,MAAA,CAAO,QACZ,MAAA,CAAO,CAAC,WAAW,CAAC,qBAAA,CAAsB,MAAA,CAAO,IAAI,CAAC,CAAA,CACtD,IAAI,CAAC,MAAA,MAAY,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,OAAO,MAAA,CAAO,QAAA,EAAU,IAAA,EAAM,KAAA,EAAe,CAAE,CAAA;AACxF;AAEA,SAAS,eAAA,CAAgB,SAAsB,GAAA,EAA8D;AAC5G,EAAA,IAAI,GAAA,CAAI,MAAA,CAAO,UAAA,CAAW,gBAAgB,CAAA,EAAG;AAC7C,EAAA,IAAI,CAAC,GAAA,CAAI,MAAA,CAAO,eAAe,CAAC,GAAA,CAAI,OAAO,SAAA,EAAW;AACtD,EAAA,WAAA,CAAY,SAAS,GAAG,CAAA;AACxB,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,OAAA,CAAQ,IAAA,EAAM,CAAA;AACtC,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,SAAS,MAAA,EAAU;AACzD;AAOO,SAAS,oBAAoB,OAAA,EAA2C;AAC9E,EAAA,MAAM,EAAE,OAAA,EAAS,KAAA,EAAM,GAAI,eAAe,OAAO,CAAA;AACjD,EAAA,OAAO;AAAA,IACN,EAAA,EAAI,SAAA;AAAA,IACJ,QAAA,EAAU,CAAC,GAAA,KAAQ,eAAA,CAAgB,SAAS,GAAG,CAAA;AAAA,IAC/C,SAAA,GAAY;AACX,MAAA,IAAI,KAAA,UAAe,OAAA,EAAQ;AAAA,IAC5B;AAAA,GACD;AACD;AC9CO,SAAS,kBAAA,CAAmB,MAAgB,OAAA,EAA2C;AAC7F,EAAA,OAAOC,cAAA,CAAS,MAAM,OAAO,CAAA;AAC9B;ACLO,IAAM,2BAAA,GAA8B;AAe3C,SAAS,UAAU,KAAA,EAA6E;AAC/F,EAAA,OAAO,MAAA,CAAO,WAAA;AAAA,IACb,OAAO,OAAA,CAAQ,KAAK,EAAE,GAAA,CAAI,CAAC,CAAC,IAAA,EAAM,IAAI,CAAA,KAAM,CAAC,MAAM,EAAE,WAAA,EAAa,MAAM,IAAA,EAAM,SAAA,EAAW,MAAM,MAAM;AAAA,IAAC,CAAA,EAAG,CAAC;AAAA,GAC3G;AACD;AAEA,SAAS,aAAa,KAAA,EAA4D;AACjF,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,cAAA,CAAe,KAAK,CAAA;AAC7C,EAAA,IAAI,SAAA,KAAc,MAAA,CAAO,SAAA,IAAa,SAAA,KAAc,MAAM,OAAO,KAAA;AACjE,EAAA,OAAO,QAAQ,OAAA,CAAQ,KAAK,CAAA,CAAE,KAAA,CAAM,CAAC,GAAA,KAAQ;AAC5C,IAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,EAAU,OAAO,KAAA;AACpC,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,wBAAA,CAAyB,KAAA,EAAO,GAAG,CAAA;AAC7D,IAAA,OAAO,UAAA,KAAe,MAAA,IAAa,OAAA,IAAW,UAAA,IAAc,UAAA,CAAW,UAAA;AAAA,EACxE,CAAC,CAAA;AACF;AAEA,SAAS,cAAA,CACR,SACA,KAAA,EACoC;AACpC,EAAA,IAAI;AACH,IAAA,MAAM,QAAA,GAAW,QAAQ,UAAA,GAAaC,4BAAA,CAAiB,QAAQ,UAAA,CAAW,KAAK,CAAC,CAAA,GAAI,EAAC;AACrF,IAAA,IAAI,CAAC,YAAA,CAAa,QAAQ,GAAG,MAAM,IAAIC,4BAAgB,SAAS,CAAA;AAChE,IAAA,MAAM,OAAO,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CAAE,MAAA,CAAO,CAAC,CAAC,GAAG,CAAA,KAAM,CAAC,IAAI,UAAA,CAAW,GAAG,CAAC,CAAC,CAAA;AAC7F,IAAA,OAAO,SAAA,CAAU,EAAE,GAAG,QAAA,EAAU,MAAM,EAAA,EAAI,KAAA,CAAM,KAAK,CAAA;AAAA,EACtD,CAAA,CAAA,MAAQ;AACP,IAAA,MAAM,IAAIA,4BAAgB,SAAS,CAAA;AAAA,EACpC;AACD;AAEA,SAAS,gBAAgB,OAAA,EAAwC;AAChE,EAAA,MAAM,UAAUC,mCAAA,CAAwB;AAAA,IACvC,GAAI,QAAQ,OAAA,GAAU,EAAE,SAAS,OAAA,CAAQ,OAAA,KAAY,EAAC;AAAA,IACtD,GAAI,QAAQ,SAAA,GAAY,EAAE,WAAW,OAAA,CAAQ,SAAA,KAAc;AAAC,GAC5D,CAAA;AACD,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAqB;AAC1C,EAAA,IAAI;AACH,IAAA,KAAA,MAAW,CAAC,EAAA,EAAI,UAAU,CAAA,IAAK,QAAQ,QAAA,EAAU;AAChD,MAAA,IAAI,OAAO,EAAA,KAAO,QAAA,EAAU,MAAM,IAAID,4BAAgB,eAAe,CAAA;AACrE,MAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,OAAA,CAAQ,UAAU,CAAA;AAC3C,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,MAAM,IAAIA,4BAAgB,QAAA,CAAS,WAAA,CAAY,CAAC,CAAA,CAAE,IAAI,CAAA;AACxE,MAAA,QAAA,CAAS,GAAA,CAAI,EAAA,EAAI,QAAA,CAAS,KAAK,CAAA;AAAA,IAChC;AACA,IAAA,OAAO,EAAE,SAAS,QAAA,EAAS;AAAA,EAC5B,SAAS,KAAA,EAAO;AACf,IAAA,OAAA,CAAQ,OAAA,EAAQ;AAChB,IAAA,QAAA,CAAS,KAAA,EAAM;AACf,IAAA,MAAM,KAAA;AAAA,EACP;AACD;AAGO,SAAS,8BAAA,CACf,UACA,OAAA,EACmC;AACnC,EAAA,IAAI,SAAS,GAAA,CAAI,2BAA2B,GAAG,MAAM,IAAIA,4BAAgB,eAAe,CAAA;AACxF,EAAA,MAAM,EAAE,OAAA,EAAS,QAAA,EAAS,GAAI,gBAAgB,OAAO,CAAA;AACrD,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,MAAM,OAAA,GAA+B,CAAC,OAAA,EAAS,KAAA,EAAO,KAAA,KAAU;AAC/D,IAAA,IAAI,QAAA,EAAU,MAAM,IAAIA,2BAAA,CAAgB,UAAU,CAAA;AAClD,IAAA,MAAM,WAA8C,EAAC;AACrD,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,EAAE,CAAA,IAAK,OAAA,EAAS;AACjC,MAAA,IAAI,OAAO,EAAA,KAAO,QAAA,EAAU,MAAM,IAAIA,4BAAgB,eAAe,CAAA;AACrE,MAAA,MAAM,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,EAAE,CAAA;AAC/B,MAAA,IAAI,CAAC,OAAA,EAAS,MAAM,IAAIA,4BAAgB,iBAAiB,CAAA;AACzD,MAAA,QAAA,CAAS,IAAA,CAAK,CAAC,IAAA,EAAM,OAAO,CAAC,CAAA;AAAA,IAC9B;AACA,IAAA,MAAM,OAAA,GAAU,cAAA,CAAe,OAAA,EAAS,KAAK,CAAA;AAC7C,IAAA,MAAM,SAAS,QAAA,CAAS,GAAA,CAAI,CAAC,GAAG,OAAO,CAAA,KAAM;AAC5C,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,QAAA,CAAS,OAAA,EAAS,OAAO,CAAA;AAChD,MAAA,IAAI,CAAC,MAAA,CAAO,EAAA,EAAI,MAAM,IAAIA,4BAAgB,MAAA,CAAO,WAAA,CAAY,CAAC,CAAA,CAAE,IAAI,CAAA;AACpE,MAAA,OAAO,MAAA,CAAO,KAAA;AAAA,IACf,CAAC,CAAA;AACD,IAAA,KAAA,IAAS,KAAA,GAAQ,CAAA,EAAG,KAAA,GAAQ,QAAA,CAAS,QAAQ,KAAA,EAAA,EAAS,KAAA,CAAM,QAAA,CAAS,KAAK,CAAA,CAAE,CAAC,CAAA,EAAG,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,EAC9F,CAAA;AACA,EAAA,IAAI;AACH,IAAA,QAAA,CAAS,QAAA,CAAS,6BAA6B,OAAO,CAAA;AAAA,EACvD,CAAA,CAAA,MAAQ;AACP,IAAA,OAAA,CAAQ,OAAA,EAAQ;AAChB,IAAA,QAAA,CAAS,KAAA,EAAM;AACf,IAAA,MAAM,IAAIA,4BAAgB,SAAS,CAAA;AAAA,EACpC;AACA,EAAA,OAAO;AAAA,IACN,OAAA;AAAA,IACA,SAAS,MAAM;AACd,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,QAAA,GAAW,IAAA;AACX,MAAA,OAAA,CAAQ,OAAA,EAAQ;AAChB,MAAA,QAAA,CAAS,KAAA,EAAM;AAAA,IAChB;AAAA,GACD;AACD","file":"index.cjs","sourcesContent":["/**\n * Arbiter-internal paths start with $ and are not user-facing form data.\n * Filter them out of write intents.\n */\nexport function isArbiterInternalPath(path: string): boolean {\n\treturn path.startsWith(\"$\") && !path.startsWith(\"$ui.\");\n}\n","import { createSession } from \"@arbitre/core\";\nimport type { FiringResult, ProductionRule, RuleSession } from \"@arbitre/core\";\nimport type { FormPlugin, PluginEvaluateContext, PluginEvaluateResult, PluginWrite } from \"@formbar/core\";\nimport { isArbiterInternalPath } from \"./internal-paths.js\";\n\nexport interface ArbiterPluginOptions {\n\t/** Provide raw rules — a session will be created internally. */\n\treadonly rules?: readonly ProductionRule[];\n\t/** Provide a pre-configured session instead of raw rules. */\n\treadonly session?: RuleSession;\n}\n\nfunction resolveSession(options: ArbiterPluginOptions): { session: RuleSession; owned: boolean } {\n\tif (options.session) return { session: options.session, owned: false };\n\tif (options.rules) return { session: createSession({ rules: options.rules as ProductionRule[] }), owned: true };\n\tthrow new Error(\"createArbiterPlugin requires either `rules` or `session`\");\n}\n\nfunction syncSession(session: RuleSession, ctx: PluginEvaluateContext): void {\n\tconst data = ctx.data as Record<string, unknown>;\n\tfor (const key of Object.keys(data)) session.assert(key, data[key]);\n\tconst uiState = ctx.uiState as Record<string, unknown>;\n\tfor (const key of Object.keys(uiState)) session.assert(`$ui.${key}`, uiState[key]);\n}\n\nfunction toWrites(result: FiringResult): readonly PluginWrite[] {\n\treturn result.changes\n\t\t.filter((change) => !isArbiterInternalPath(change.path))\n\t\t.map((change) => ({ path: change.path, value: change.newValue, mode: \"set\" as const }));\n}\n\nfunction evaluateSession(session: RuleSession, ctx: PluginEvaluateContext): PluginEvaluateResult | undefined {\n\tif (ctx.origin.startsWith(\"plugin:arbiter\")) return;\n\tif (!ctx.change.dataChanged && !ctx.change.uiChanged) return;\n\tsyncSession(session, ctx);\n\tconst writes = toWrites(session.fire());\n\treturn { writes: writes.length > 0 ? writes : undefined };\n}\n\n/**\n * Creates a FormPlugin that bridges @arbitre/core into the formbar pipeline.\n * Syncs form data into the rule session, fires rules, and converts results\n * into PluginWrite[] records.\n */\nexport function createArbiterPlugin(options: ArbiterPluginOptions): FormPlugin {\n\tconst { session, owned } = resolveSession(options);\n\treturn {\n\t\tid: \"arbiter\",\n\t\tevaluate: (ctx) => evaluateSession(session, ctx),\n\t\tonDispose() {\n\t\t\tif (owned) session.dispose();\n\t\t},\n\t};\n}\n","import { assertSafeSegment, evaluate } from \"kuery\";\nimport type { ExprNode } from \"kuery\";\n\n/**\n * Evaluate a kuery expression against a data context.\n * This is a convenience re-export for consumers using expressions with arbiter rules.\n */\nexport function evaluateExpression(expr: ExprNode, context: Record<string, unknown>): unknown {\n\treturn evaluate(expr, context);\n}\n\nexport { assertSafeSegment };\nexport type { ExprNode };\n","import type { ThenOperatorHandler, ThenOperatorRegistry } from \"@arbitre/core\";\nimport { ExpressionError, createExpressionService, synchronousValue } from \"@formbar/expressions\";\nimport type { Authorization, Expression, ExpressionProfile, NamespaceProvider, Program } from \"@formbar/expressions\";\n\nexport const FORMBAR_VALUE_THEN_OPERATOR = \"$formbarValue\";\n\nexport interface ExpressionThenOperatorOptions {\n\treadonly programs: ReadonlyMap<string, Expression>;\n\treadonly profile?: ExpressionProfile;\n\treadonly authorize?: Authorization;\n\t/** Select trusted external roots from the current incoming stage scope. */\n\treadonly namespaces?: (scope: Readonly<Record<string, unknown>>) => Readonly<Record<string, unknown>>;\n}\n\nexport interface RegisteredExpressionThenOperator {\n\treadonly handler: ThenOperatorHandler;\n\tdispose(): void;\n}\n\nfunction providers(roots: Readonly<Record<string, unknown>>): Record<string, NamespaceProvider> {\n\treturn Object.fromEntries(\n\t\tObject.entries(roots).map(([name, root]) => [name, { getSnapshot: () => root, subscribe: () => () => {} }]),\n\t);\n}\n\nfunction plainRootMap(value: unknown): value is Readonly<Record<string, unknown>> {\n\tif (value === null || typeof value !== \"object\") return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (prototype !== Object.prototype && prototype !== null) return false;\n\treturn Reflect.ownKeys(value).every((key) => {\n\t\tif (typeof key !== \"string\") return false;\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, key);\n\t\treturn descriptor !== undefined && \"value\" in descriptor && descriptor.enumerable;\n\t});\n}\n\nfunction scopeProviders(\n\toptions: ExpressionThenOperatorOptions,\n\tscope: Readonly<Record<string, unknown>>,\n): Record<string, NamespaceProvider> {\n\ttry {\n\t\tconst selected = options.namespaces ? synchronousValue(options.namespaces(scope)) : {};\n\t\tif (!plainRootMap(selected)) throw new ExpressionError(\"adapter\");\n\t\tconst data = Object.fromEntries(Object.entries(scope).filter(([key]) => !key.startsWith(\"$\")));\n\t\treturn providers({ ...selected, data, ui: scope.$ui });\n\t} catch {\n\t\tthrow new ExpressionError(\"adapter\");\n\t}\n}\n\nfunction compilePrograms(options: ExpressionThenOperatorOptions) {\n\tconst service = createExpressionService({\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t\t...(options.authorize ? { authorize: options.authorize } : {}),\n\t});\n\tconst programs = new Map<string, Program>();\n\ttry {\n\t\tfor (const [id, expression] of options.programs) {\n\t\t\tif (typeof id !== \"string\") throw new ExpressionError(\"invalid-input\");\n\t\t\tconst compiled = service.compile(expression);\n\t\t\tif (!compiled.ok) throw new ExpressionError(compiled.diagnostics[0].code);\n\t\t\tprograms.set(id, compiled.value);\n\t\t}\n\t\treturn { service, programs };\n\t} catch (error) {\n\t\tservice.dispose();\n\t\tprograms.clear();\n\t\tthrow error;\n\t}\n}\n\n/** Registers Formbar's atomic expression stage in an Arbitre 0.3 public registry. */\nexport function registerExpressionThenOperator(\n\tregistry: ThenOperatorRegistry,\n\toptions: ExpressionThenOperatorOptions,\n): RegisteredExpressionThenOperator {\n\tif (registry.has(FORMBAR_VALUE_THEN_OPERATOR)) throw new ExpressionError(\"invalid-input\");\n\tconst { service, programs } = compilePrograms(options);\n\tlet disposed = false;\n\tconst handler: ThenOperatorHandler = (entries, scope, write) => {\n\t\tif (disposed) throw new ExpressionError(\"disposed\");\n\t\tconst selected: Array<readonly [string, Program]> = [];\n\t\tfor (const [path, id] of entries) {\n\t\t\tif (typeof id !== \"string\") throw new ExpressionError(\"invalid-input\");\n\t\t\tconst program = programs.get(id);\n\t\t\tif (!program) throw new ExpressionError(\"unknown-program\");\n\t\t\tselected.push([path, program]);\n\t\t}\n\t\tconst context = scopeProviders(options, scope);\n\t\tconst values = selected.map(([, program]) => {\n\t\t\tconst result = service.evaluate(program, context);\n\t\t\tif (!result.ok) throw new ExpressionError(result.diagnostics[0].code);\n\t\t\treturn result.value;\n\t\t});\n\t\tfor (let index = 0; index < selected.length; index++) write(selected[index][0], values[index]);\n\t};\n\ttry {\n\t\tregistry.register(FORMBAR_VALUE_THEN_OPERATOR, handler);\n\t} catch {\n\t\tservice.dispose();\n\t\tprograms.clear();\n\t\tthrow new ExpressionError(\"adapter\");\n\t}\n\treturn {\n\t\thandler,\n\t\tdispose: () => {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\t\t\tservice.dispose();\n\t\t\tprograms.clear();\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"sources":["../src/field-policy-output.ts","../src/internal-paths.ts","../src/arbiter-plugin.ts","../src/expression-utils.ts","../src/expression-then-operator.ts"],"names":["ArbiterError","ArbiterErrorCode","parsePath","createSession","evaluate","synchronousValue","ExpressionError","createExpressionService"],"mappings":"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","file":"index.cjs","sourcesContent":["import { ArbiterError, ArbiterErrorCode } from \"@arbitre/core\";\nimport { type FieldPolicyInput, parsePath } from \"@formbar/core\";\n\nconst OUTPUT_ROOT = \"$formbar.fieldPolicy\";\nconst OUTPUT_ID = /^[A-Za-z][A-Za-z0-9_-]*$/;\nconst POLICY_KEYS = new Set<PropertyKey>([\"path\", \"visible\", \"disabled\", \"readOnly\", \"required\", \"label\"]);\nconst BOOLEAN_KEYS = [\"visible\", \"disabled\", \"readOnly\", \"required\"] as const;\nconst UNSAFE_SEGMENTS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\nconst INTEGER_SEGMENT = /^(?:0|[1-9]\\d*)$/;\n\ninterface CapturedRecord {\n\treadonly keys: readonly string[];\n\treadonly values: ReadonlyMap<string, unknown>;\n}\n\nfunction outputError(message: string, outputId?: string): never {\n\tthrow new ArbiterError(ArbiterErrorCode.RULE_COMPILATION_FAILED, message, {\n\t\tdetails: outputId === undefined ? { root: OUTPUT_ROOT } : { root: OUTPUT_ROOT, outputId },\n\t});\n}\n\nfunction pathError(message: string, outputId: string, path: unknown): never {\n\tthrow new ArbiterError(ArbiterErrorCode.INVALID_PATH, message, {\n\t\tdetails: { root: OUTPUT_ROOT, outputId, path },\n\t});\n}\n\nfunction captureRecord(value: unknown, outputId?: string): CapturedRecord {\n\tif (value === null || typeof value !== \"object\") {\n\t\treturn outputError(\"Arbiter field policy output must be a plain data record\", outputId);\n\t}\n\tlet array: boolean;\n\tlet prototype: object | null;\n\tlet keys: readonly PropertyKey[];\n\tconst descriptors = new Map<PropertyKey, PropertyDescriptor | undefined>();\n\ttry {\n\t\tarray = Array.isArray(value);\n\t\tprototype = Reflect.getPrototypeOf(value);\n\t\tkeys = Reflect.ownKeys(value);\n\t\tfor (const key of keys) descriptors.set(key, Reflect.getOwnPropertyDescriptor(value, key));\n\t} catch {\n\t\treturn outputError(\"Arbiter field policy output must be inspectable\", outputId);\n\t}\n\tif (array) return outputError(\"Arbiter field policy output must be a plain data record\", outputId);\n\tif (prototype !== Object.prototype && prototype !== null) {\n\t\treturn outputError(\"Arbiter field policy output must use a plain prototype\", outputId);\n\t}\n\tconst values = new Map<string, unknown>();\n\tfor (const key of keys) {\n\t\tconst descriptor = descriptors.get(key);\n\t\tif (typeof key !== \"string\" || !descriptor?.enumerable || !(\"value\" in descriptor)) {\n\t\t\treturn outputError(\"Arbiter field policy output requires enumerable data properties\", outputId);\n\t\t}\n\t\tvalues.set(key, descriptor.value);\n\t}\n\treturn { keys: [...values.keys()], values };\n}\n\nfunction normalizedPathKey(path: unknown, outputId: string): string {\n\tif (typeof path !== \"string\" || !path.startsWith(\"/\") || path === \"/\") {\n\t\treturn pathError(`Field policy output \"${outputId}\" path must be a non-empty RFC-6901 pointer`, outputId, path);\n\t}\n\tlet segments: readonly (string | number)[];\n\ttry {\n\t\tsegments = parsePath(path).segments;\n\t} catch {\n\t\treturn pathError(`Field policy output \"${outputId}\" has an invalid RFC-6901 pointer`, outputId, path);\n\t}\n\tif (segments.some((segment) => segment === \"\" || segment === \"*\" || UNSAFE_SEGMENTS.has(String(segment)))) {\n\t\treturn pathError(`Field policy output \"${outputId}\" has an unsupported target path`, outputId, path);\n\t}\n\tconst normalized = segments.map((segment) => (INTEGER_SEGMENT.test(String(segment)) ? Number(segment) : segment));\n\tif (normalized.some((segment) => typeof segment === \"number\" && !Number.isSafeInteger(segment))) {\n\t\treturn pathError(`Field policy output \"${outputId}\" has an unsafe array index`, outputId, path);\n\t}\n\treturn JSON.stringify(normalized);\n}\n\nfunction decodeRecord(\n\tvalue: unknown,\n\toutputId: string,\n): { readonly input: FieldPolicyInput; readonly pathKey: string } {\n\tconst { keys, values } = captureRecord(value, outputId);\n\tfor (const key of keys) {\n\t\tif (!POLICY_KEYS.has(key)) outputError(`Field policy output \"${outputId}\" has unknown property \"${key}\"`, outputId);\n\t}\n\tif (!keys.includes(\"path\")) outputError(`Field policy output \"${outputId}\" requires \"path\"`, outputId);\n\tif (!keys.some((key) => key !== \"path\")) {\n\t\toutputError(`Field policy output \"${outputId}\" requires at least one policy property`, outputId);\n\t}\n\tfor (const key of BOOLEAN_KEYS) {\n\t\tif (values.has(key) && typeof values.get(key) !== \"boolean\") {\n\t\t\toutputError(`Field policy output \"${outputId}\" property \"${key}\" must be boolean`, outputId);\n\t\t}\n\t}\n\tif (values.has(\"label\") && typeof values.get(\"label\") !== \"string\") {\n\t\toutputError(`Field policy output \"${outputId}\" property \"label\" must be a string`, outputId);\n\t}\n\tconst path = values.get(\"path\");\n\tconst pathKey = normalizedPathKey(path, outputId);\n\tconst input: FieldPolicyInput = Object.freeze({\n\t\tpath: path as string,\n\t\t...(values.has(\"visible\") ? { visible: values.get(\"visible\") as boolean } : {}),\n\t\t...(values.has(\"disabled\") ? { disabled: values.get(\"disabled\") as boolean } : {}),\n\t\t...(values.has(\"readOnly\") ? { readOnly: values.get(\"readOnly\") as boolean } : {}),\n\t\t...(values.has(\"required\") ? { required: values.get(\"required\") as boolean } : {}),\n\t\t...(values.has(\"label\") ? { label: values.get(\"label\") as string } : {}),\n\t});\n\treturn { input, pathKey };\n}\n\nexport function readFieldPolicyOutput(session: { getPath(path: string): unknown }): readonly FieldPolicyInput[] {\n\tlet root: unknown;\n\ttry {\n\t\troot = session.getPath(OUTPUT_ROOT);\n\t} catch (error) {\n\t\tif (error instanceof ArbiterError) throw error;\n\t\treturn outputError(\"Arbiter field policy output root could not be read\");\n\t}\n\tif (root === undefined) return Object.freeze([]);\n\tconst captured = captureRecord(root);\n\tconst outputIds = [...captured.keys].sort();\n\tconst seen = new Map<string, string>();\n\tconst inputs = outputIds.map((outputId) => {\n\t\tif (!OUTPUT_ID.test(outputId)) outputError(`Invalid field policy output ID \"${outputId}\"`, outputId);\n\t\tconst decoded = decodeRecord(captured.values.get(outputId), outputId);\n\t\tconst duplicate = seen.get(decoded.pathKey);\n\t\tif (duplicate) {\n\t\t\tpathError(\n\t\t\t\t`Field policy outputs \"${duplicate}\" and \"${outputId}\" target the same path`,\n\t\t\t\toutputId,\n\t\t\t\tdecoded.input.path,\n\t\t\t);\n\t\t}\n\t\tseen.set(decoded.pathKey, outputId);\n\t\treturn decoded.input;\n\t});\n\treturn Object.freeze(inputs);\n}\n","/**\n * Arbiter-internal paths start with $ and are not user-facing form data.\n * Filter them out of write intents.\n */\nexport function isArbiterInternalPath(path: string): boolean {\n\treturn path.startsWith(\"$\") && !path.startsWith(\"$ui.\");\n}\n","import { ArbiterError, ArbiterErrorCode, createSession } from \"@arbitre/core\";\nimport type { FiringResult, ProductionRule, RuleSession } from \"@arbitre/core\";\nimport type { FormPlugin, PluginEvaluateContext, PluginEvaluateResult, PluginWrite } from \"@formbar/core\";\nimport { readFieldPolicyOutput } from \"./field-policy-output.js\";\nimport { isArbiterInternalPath } from \"./internal-paths.js\";\n\nconst RESERVED_DATA_ROOT = \"$formbar\";\n\nexport interface ArbiterPluginOptions {\n\t/** Provide raw rules — a session will be created internally. */\n\treadonly rules?: readonly ProductionRule[];\n\t/** Provide a pre-configured session instead of raw rules. */\n\treadonly session?: RuleSession;\n}\n\nfunction resolveSession(options: ArbiterPluginOptions): { session: RuleSession; owned: boolean } {\n\tif (options.session) return { session: options.session, owned: false };\n\tif (options.rules) return { session: createSession({ rules: options.rules as ProductionRule[] }), owned: true };\n\tthrow new Error(\"createArbiterPlugin requires either `rules` or `session`\");\n}\n\ninterface SynchronizedRoots {\n\tdata: Set<string>;\n\tui: Set<string>;\n}\n\nfunction syncRoots(\n\tsession: RuleSession,\n\tvalues: Record<string, unknown>,\n\tprevious: Set<string>,\n\tprefix: string,\n\tinclude: (key: string) => boolean = () => true,\n): Set<string> {\n\tconst current = new Set(Object.keys(values).filter(include));\n\tfor (const key of previous) {\n\t\tif (!current.has(key)) session.retract(`${prefix}${key}`);\n\t}\n\tfor (const key of current) session.assert(`${prefix}${key}`, values[key]);\n\treturn current;\n}\n\nfunction isFormDataRoot(key: string): boolean {\n\treturn key !== RESERVED_DATA_ROOT && !key.startsWith(`${RESERVED_DATA_ROOT}.`);\n}\n\nfunction syncSession(session: RuleSession, ctx: PluginEvaluateContext, roots: SynchronizedRoots): void {\n\troots.data = syncRoots(session, ctx.data as Record<string, unknown>, roots.data, \"\", isFormDataRoot);\n\troots.ui = syncRoots(session, ctx.uiState as Record<string, unknown>, roots.ui, \"$ui.\");\n}\n\nfunction fireSession(session: RuleSession): FiringResult {\n\ttry {\n\t\treturn session.fire();\n\t} catch (error) {\n\t\tif (error instanceof ArbiterError) throw error;\n\t\tthrow new ArbiterError(\n\t\t\tArbiterErrorCode.RULE_COMPILATION_FAILED,\n\t\t\t\"Arbiter session state could not be evaluated safely\",\n\t\t\terror instanceof Error\n\t\t\t\t? { details: { root: \"$formbar.fieldPolicy\" }, cause: error }\n\t\t\t\t: { details: { root: \"$formbar.fieldPolicy\" } },\n\t\t);\n\t}\n}\n\nfunction toWrites(result: FiringResult): readonly PluginWrite[] {\n\treturn result.changes\n\t\t.filter((change) => !isArbiterInternalPath(change.path))\n\t\t.map((change) => ({ path: change.path, value: change.newValue, mode: \"set\" as const }));\n}\n\nfunction evaluateSession(\n\tsession: RuleSession,\n\tctx: PluginEvaluateContext,\n\troots: SynchronizedRoots,\n): PluginEvaluateResult | undefined {\n\tif (ctx.origin.startsWith(\"plugin:arbiter\")) return;\n\tif (!ctx.change.dataChanged && !ctx.change.uiChanged) return;\n\tsyncSession(session, ctx, roots);\n\tconst writes = toWrites(fireSession(session));\n\treturn { writes: writes.length > 0 ? writes : undefined, fieldPolicy: readFieldPolicyOutput(session) };\n}\n\n/**\n * Creates a FormPlugin that bridges @arbitre/core into the formbar pipeline.\n * Syncs form data into the rule session, fires rules, and converts results\n * into PluginWrite[] records.\n */\nexport function createArbiterPlugin(options: ArbiterPluginOptions): FormPlugin {\n\tconst { session, owned } = resolveSession(options);\n\tconst roots: SynchronizedRoots = { data: new Set(), ui: new Set() };\n\treturn {\n\t\tid: \"arbiter\",\n\t\tevaluate: (ctx) => evaluateSession(session, ctx, roots),\n\t\tonDispose() {\n\t\t\tif (owned) session.dispose();\n\t\t},\n\t};\n}\n","import { assertSafeSegment, evaluate } from \"kuery\";\nimport type { ExprNode } from \"kuery\";\n\n/**\n * Evaluate a kuery expression against a data context.\n * This is a convenience re-export for consumers using expressions with arbiter rules.\n */\nexport function evaluateExpression(expr: ExprNode, context: Record<string, unknown>): unknown {\n\treturn evaluate(expr, context);\n}\n\nexport { assertSafeSegment };\nexport type { ExprNode };\n","import type { ThenOperatorHandler, ThenOperatorRegistry } from \"@arbitre/core\";\nimport { ExpressionError, createExpressionService, synchronousValue } from \"@formbar/expressions\";\nimport type { Authorization, Expression, ExpressionProfile, NamespaceProvider, Program } from \"@formbar/expressions\";\n\nexport const FORMBAR_VALUE_THEN_OPERATOR = \"$formbarValue\";\n\nexport interface ExpressionThenOperatorOptions {\n\treadonly programs: ReadonlyMap<string, Expression>;\n\treadonly profile?: ExpressionProfile;\n\treadonly authorize?: Authorization;\n\t/** Select trusted external roots from the current incoming stage scope. */\n\treadonly namespaces?: (scope: Readonly<Record<string, unknown>>) => Readonly<Record<string, unknown>>;\n}\n\nexport interface RegisteredExpressionThenOperator {\n\treadonly handler: ThenOperatorHandler;\n\tdispose(): void;\n}\n\nfunction providers(roots: Readonly<Record<string, unknown>>): Record<string, NamespaceProvider> {\n\treturn Object.fromEntries(\n\t\tObject.entries(roots).map(([name, root]) => [name, { getSnapshot: () => root, subscribe: () => () => {} }]),\n\t);\n}\n\nfunction plainRootMap(value: unknown): value is Readonly<Record<string, unknown>> {\n\tif (value === null || typeof value !== \"object\") return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (prototype !== Object.prototype && prototype !== null) return false;\n\treturn Reflect.ownKeys(value).every((key) => {\n\t\tif (typeof key !== \"string\") return false;\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(value, key);\n\t\treturn descriptor !== undefined && \"value\" in descriptor && descriptor.enumerable;\n\t});\n}\n\nfunction scopeProviders(\n\toptions: ExpressionThenOperatorOptions,\n\tscope: Readonly<Record<string, unknown>>,\n): Record<string, NamespaceProvider> {\n\ttry {\n\t\tconst selected = options.namespaces ? synchronousValue(options.namespaces(scope)) : {};\n\t\tif (!plainRootMap(selected)) throw new ExpressionError(\"adapter\");\n\t\tconst data = Object.fromEntries(Object.entries(scope).filter(([key]) => !key.startsWith(\"$\")));\n\t\treturn providers({ ...selected, data, ui: scope.$ui });\n\t} catch {\n\t\tthrow new ExpressionError(\"adapter\");\n\t}\n}\n\nfunction compilePrograms(options: ExpressionThenOperatorOptions) {\n\tconst service = createExpressionService({\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t\t...(options.authorize ? { authorize: options.authorize } : {}),\n\t});\n\tconst programs = new Map<string, Program>();\n\ttry {\n\t\tfor (const [id, expression] of options.programs) {\n\t\t\tif (typeof id !== \"string\") throw new ExpressionError(\"invalid-input\");\n\t\t\tconst compiled = service.compile(expression);\n\t\t\tif (!compiled.ok) throw new ExpressionError(compiled.diagnostics[0].code);\n\t\t\tprograms.set(id, compiled.value);\n\t\t}\n\t\treturn { service, programs };\n\t} catch (error) {\n\t\tservice.dispose();\n\t\tprograms.clear();\n\t\tthrow error;\n\t}\n}\n\n/** Registers Formbar's atomic expression stage in an Arbitre 0.3 public registry. */\nexport function registerExpressionThenOperator(\n\tregistry: ThenOperatorRegistry,\n\toptions: ExpressionThenOperatorOptions,\n): RegisteredExpressionThenOperator {\n\tif (registry.has(FORMBAR_VALUE_THEN_OPERATOR)) throw new ExpressionError(\"invalid-input\");\n\tconst { service, programs } = compilePrograms(options);\n\tlet disposed = false;\n\tconst handler: ThenOperatorHandler = (entries, scope, write) => {\n\t\tif (disposed) throw new ExpressionError(\"disposed\");\n\t\tconst selected: Array<readonly [string, Program]> = [];\n\t\tfor (const [path, id] of entries) {\n\t\t\tif (typeof id !== \"string\") throw new ExpressionError(\"invalid-input\");\n\t\t\tconst program = programs.get(id);\n\t\t\tif (!program) throw new ExpressionError(\"unknown-program\");\n\t\t\tselected.push([path, program]);\n\t\t}\n\t\tconst context = scopeProviders(options, scope);\n\t\tconst values = selected.map(([, program]) => {\n\t\t\tconst result = service.evaluate(program, context);\n\t\t\tif (!result.ok) throw new ExpressionError(result.diagnostics[0].code);\n\t\t\treturn result.value;\n\t\t});\n\t\tfor (let index = 0; index < selected.length; index++) write(selected[index][0], values[index]);\n\t};\n\ttry {\n\t\tregistry.register(FORMBAR_VALUE_THEN_OPERATOR, handler);\n\t} catch {\n\t\tservice.dispose();\n\t\tprograms.clear();\n\t\tthrow new ExpressionError(\"adapter\");\n\t}\n\treturn {\n\t\thandler,\n\t\tdispose: () => {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\t\t\tservice.dispose();\n\t\t\tprograms.clear();\n\t\t},\n\t};\n}\n"]}
package/dist/index.js CHANGED
@@ -1,9 +1,132 @@
1
- import { createSession } from '@arbitre/core';
1
+ import { createSession, ArbiterError, ArbiterErrorCode } from '@arbitre/core';
2
+ import { parsePath } from '@formbar/core';
2
3
  import { evaluate } from 'kuery';
3
4
  export { assertSafeSegment } from 'kuery';
4
5
  import { ExpressionError, createExpressionService, synchronousValue } from '@formbar/expressions';
5
6
 
6
7
  // src/arbiter-plugin.ts
8
+ var OUTPUT_ROOT = "$formbar.fieldPolicy";
9
+ var OUTPUT_ID = /^[A-Za-z][A-Za-z0-9_-]*$/;
10
+ var POLICY_KEYS = /* @__PURE__ */ new Set(["path", "visible", "disabled", "readOnly", "required", "label"]);
11
+ var BOOLEAN_KEYS = ["visible", "disabled", "readOnly", "required"];
12
+ var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
13
+ var INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
14
+ function outputError(message, outputId) {
15
+ throw new ArbiterError(ArbiterErrorCode.RULE_COMPILATION_FAILED, message, {
16
+ details: outputId === void 0 ? { root: OUTPUT_ROOT } : { root: OUTPUT_ROOT, outputId }
17
+ });
18
+ }
19
+ function pathError(message, outputId, path) {
20
+ throw new ArbiterError(ArbiterErrorCode.INVALID_PATH, message, {
21
+ details: { root: OUTPUT_ROOT, outputId, path }
22
+ });
23
+ }
24
+ function captureRecord(value, outputId) {
25
+ if (value === null || typeof value !== "object") {
26
+ return outputError("Arbiter field policy output must be a plain data record", outputId);
27
+ }
28
+ let array;
29
+ let prototype;
30
+ let keys;
31
+ const descriptors = /* @__PURE__ */ new Map();
32
+ try {
33
+ array = Array.isArray(value);
34
+ prototype = Reflect.getPrototypeOf(value);
35
+ keys = Reflect.ownKeys(value);
36
+ for (const key of keys) descriptors.set(key, Reflect.getOwnPropertyDescriptor(value, key));
37
+ } catch {
38
+ return outputError("Arbiter field policy output must be inspectable", outputId);
39
+ }
40
+ if (array) return outputError("Arbiter field policy output must be a plain data record", outputId);
41
+ if (prototype !== Object.prototype && prototype !== null) {
42
+ return outputError("Arbiter field policy output must use a plain prototype", outputId);
43
+ }
44
+ const values = /* @__PURE__ */ new Map();
45
+ for (const key of keys) {
46
+ const descriptor = descriptors.get(key);
47
+ if (typeof key !== "string" || !descriptor?.enumerable || !("value" in descriptor)) {
48
+ return outputError("Arbiter field policy output requires enumerable data properties", outputId);
49
+ }
50
+ values.set(key, descriptor.value);
51
+ }
52
+ return { keys: [...values.keys()], values };
53
+ }
54
+ function normalizedPathKey(path, outputId) {
55
+ if (typeof path !== "string" || !path.startsWith("/") || path === "/") {
56
+ return pathError(`Field policy output "${outputId}" path must be a non-empty RFC-6901 pointer`, outputId, path);
57
+ }
58
+ let segments;
59
+ try {
60
+ segments = parsePath(path).segments;
61
+ } catch {
62
+ return pathError(`Field policy output "${outputId}" has an invalid RFC-6901 pointer`, outputId, path);
63
+ }
64
+ if (segments.some((segment) => segment === "" || segment === "*" || UNSAFE_SEGMENTS.has(String(segment)))) {
65
+ return pathError(`Field policy output "${outputId}" has an unsupported target path`, outputId, path);
66
+ }
67
+ const normalized = segments.map((segment) => INTEGER_SEGMENT.test(String(segment)) ? Number(segment) : segment);
68
+ if (normalized.some((segment) => typeof segment === "number" && !Number.isSafeInteger(segment))) {
69
+ return pathError(`Field policy output "${outputId}" has an unsafe array index`, outputId, path);
70
+ }
71
+ return JSON.stringify(normalized);
72
+ }
73
+ function decodeRecord(value, outputId) {
74
+ const { keys, values } = captureRecord(value, outputId);
75
+ for (const key of keys) {
76
+ if (!POLICY_KEYS.has(key)) outputError(`Field policy output "${outputId}" has unknown property "${key}"`, outputId);
77
+ }
78
+ if (!keys.includes("path")) outputError(`Field policy output "${outputId}" requires "path"`, outputId);
79
+ if (!keys.some((key) => key !== "path")) {
80
+ outputError(`Field policy output "${outputId}" requires at least one policy property`, outputId);
81
+ }
82
+ for (const key of BOOLEAN_KEYS) {
83
+ if (values.has(key) && typeof values.get(key) !== "boolean") {
84
+ outputError(`Field policy output "${outputId}" property "${key}" must be boolean`, outputId);
85
+ }
86
+ }
87
+ if (values.has("label") && typeof values.get("label") !== "string") {
88
+ outputError(`Field policy output "${outputId}" property "label" must be a string`, outputId);
89
+ }
90
+ const path = values.get("path");
91
+ const pathKey = normalizedPathKey(path, outputId);
92
+ const input = Object.freeze({
93
+ path,
94
+ ...values.has("visible") ? { visible: values.get("visible") } : {},
95
+ ...values.has("disabled") ? { disabled: values.get("disabled") } : {},
96
+ ...values.has("readOnly") ? { readOnly: values.get("readOnly") } : {},
97
+ ...values.has("required") ? { required: values.get("required") } : {},
98
+ ...values.has("label") ? { label: values.get("label") } : {}
99
+ });
100
+ return { input, pathKey };
101
+ }
102
+ function readFieldPolicyOutput(session) {
103
+ let root;
104
+ try {
105
+ root = session.getPath(OUTPUT_ROOT);
106
+ } catch (error) {
107
+ if (error instanceof ArbiterError) throw error;
108
+ return outputError("Arbiter field policy output root could not be read");
109
+ }
110
+ if (root === void 0) return Object.freeze([]);
111
+ const captured = captureRecord(root);
112
+ const outputIds = [...captured.keys].sort();
113
+ const seen = /* @__PURE__ */ new Map();
114
+ const inputs = outputIds.map((outputId) => {
115
+ if (!OUTPUT_ID.test(outputId)) outputError(`Invalid field policy output ID "${outputId}"`, outputId);
116
+ const decoded = decodeRecord(captured.values.get(outputId), outputId);
117
+ const duplicate = seen.get(decoded.pathKey);
118
+ if (duplicate) {
119
+ pathError(
120
+ `Field policy outputs "${duplicate}" and "${outputId}" target the same path`,
121
+ outputId,
122
+ decoded.input.path
123
+ );
124
+ }
125
+ seen.set(decoded.pathKey, outputId);
126
+ return decoded.input;
127
+ });
128
+ return Object.freeze(inputs);
129
+ }
7
130
 
8
131
  // src/internal-paths.ts
9
132
  function isArbiterInternalPath(path) {
@@ -11,32 +134,55 @@ function isArbiterInternalPath(path) {
11
134
  }
12
135
 
13
136
  // src/arbiter-plugin.ts
137
+ var RESERVED_DATA_ROOT = "$formbar";
14
138
  function resolveSession(options) {
15
139
  if (options.session) return { session: options.session, owned: false };
16
140
  if (options.rules) return { session: createSession({ rules: options.rules }), owned: true };
17
141
  throw new Error("createArbiterPlugin requires either `rules` or `session`");
18
142
  }
19
- function syncSession(session, ctx) {
20
- const data = ctx.data;
21
- for (const key of Object.keys(data)) session.assert(key, data[key]);
22
- const uiState = ctx.uiState;
23
- for (const key of Object.keys(uiState)) session.assert(`$ui.${key}`, uiState[key]);
143
+ function syncRoots(session, values, previous, prefix, include = () => true) {
144
+ const current = new Set(Object.keys(values).filter(include));
145
+ for (const key of previous) {
146
+ if (!current.has(key)) session.retract(`${prefix}${key}`);
147
+ }
148
+ for (const key of current) session.assert(`${prefix}${key}`, values[key]);
149
+ return current;
150
+ }
151
+ function isFormDataRoot(key) {
152
+ return key !== RESERVED_DATA_ROOT && !key.startsWith(`${RESERVED_DATA_ROOT}.`);
153
+ }
154
+ function syncSession(session, ctx, roots) {
155
+ roots.data = syncRoots(session, ctx.data, roots.data, "", isFormDataRoot);
156
+ roots.ui = syncRoots(session, ctx.uiState, roots.ui, "$ui.");
157
+ }
158
+ function fireSession(session) {
159
+ try {
160
+ return session.fire();
161
+ } catch (error) {
162
+ if (error instanceof ArbiterError) throw error;
163
+ throw new ArbiterError(
164
+ ArbiterErrorCode.RULE_COMPILATION_FAILED,
165
+ "Arbiter session state could not be evaluated safely",
166
+ error instanceof Error ? { details: { root: "$formbar.fieldPolicy" }, cause: error } : { details: { root: "$formbar.fieldPolicy" } }
167
+ );
168
+ }
24
169
  }
25
170
  function toWrites(result) {
26
171
  return result.changes.filter((change) => !isArbiterInternalPath(change.path)).map((change) => ({ path: change.path, value: change.newValue, mode: "set" }));
27
172
  }
28
- function evaluateSession(session, ctx) {
173
+ function evaluateSession(session, ctx, roots) {
29
174
  if (ctx.origin.startsWith("plugin:arbiter")) return;
30
175
  if (!ctx.change.dataChanged && !ctx.change.uiChanged) return;
31
- syncSession(session, ctx);
32
- const writes = toWrites(session.fire());
33
- return { writes: writes.length > 0 ? writes : void 0 };
176
+ syncSession(session, ctx, roots);
177
+ const writes = toWrites(fireSession(session));
178
+ return { writes: writes.length > 0 ? writes : void 0, fieldPolicy: readFieldPolicyOutput(session) };
34
179
  }
35
180
  function createArbiterPlugin(options) {
36
181
  const { session, owned } = resolveSession(options);
182
+ const roots = { data: /* @__PURE__ */ new Set(), ui: /* @__PURE__ */ new Set() };
37
183
  return {
38
184
  id: "arbiter",
39
- evaluate: (ctx) => evaluateSession(session, ctx),
185
+ evaluate: (ctx) => evaluateSession(session, ctx, roots),
40
186
  onDispose() {
41
187
  if (owned) session.dispose();
42
188
  }