@formbar/arbiter 0.8.0 → 0.12.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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal-paths.ts","../src/arbiter-plugin.ts","../src/expression-utils.ts","../src/expression-then-operator.ts"],"names":[],"mappings":"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","file":"index.js","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"],"mappings":"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","file":"index.js","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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formbar/arbiter",
3
- "version": "0.8.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "Arbiter rule engine bridge for @formbar/core — declarative UI governance via production rules",
6
6
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@formbar/expressions": "^0.4.0",
31
- "@formbar/core": "^0.8.0"
31
+ "@formbar/core": "^0.12.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@arbitre/core": "^0.3.0",
@@ -5,8 +5,8 @@ import { createArbiterPlugin } from "../arbiter-plugin.js";
5
5
 
6
6
  /**
7
7
  * Reproduction test for demo 18 (arbiter visibility).
8
- * Uses the exact same rules and initial state as the demo component
9
- * to verify core logic works end-to-end through createForm + arbiter plugin.
8
+ * Uses the same country transitions as the historical demo while expressing
9
+ * visibility through normalized field policy.
10
10
  */
11
11
 
12
12
  interface FormData {
@@ -16,52 +16,71 @@ interface FormData {
16
16
  readonly region: string;
17
17
  }
18
18
 
19
- interface UiState {
20
- readonly showState: boolean;
21
- readonly showProvince: boolean;
22
- }
23
-
24
19
  const arbiterRules: readonly ProductionRule[] = [
25
20
  {
26
21
  name: "showUSState",
27
22
  when: { country: "US" },
28
- then: [{ $set: { "$ui.showState": true, "$ui.showProvince": false } }],
23
+ then: [
24
+ {
25
+ $set: {
26
+ "$formbar.fieldPolicy.state": { path: "/state", visible: true },
27
+ "$formbar.fieldPolicy.province": { path: "/province", visible: false },
28
+ },
29
+ },
30
+ ],
29
31
  },
30
32
  {
31
33
  name: "showCAProvince",
32
34
  when: { country: "CA" },
33
- then: [{ $set: { "$ui.showState": false, "$ui.showProvince": true } }],
35
+ then: [
36
+ {
37
+ $set: {
38
+ "$formbar.fieldPolicy.state": { path: "/state", visible: false },
39
+ "$formbar.fieldPolicy.province": { path: "/province", visible: true },
40
+ },
41
+ },
42
+ ],
34
43
  },
35
44
  {
36
45
  name: "hideRegional",
37
46
  when: { country: { $nin: ["US", "CA"] } },
38
- then: [{ $set: { "$ui.showState": false, "$ui.showProvince": false } }],
47
+ then: [
48
+ {
49
+ $set: {
50
+ "$formbar.fieldPolicy.state": { path: "/state", visible: false },
51
+ "$formbar.fieldPolicy.province": { path: "/province", visible: false },
52
+ },
53
+ },
54
+ ],
39
55
  },
40
56
  ];
41
57
 
42
58
  function makeForm() {
43
- return createForm<FormData, UiState>({
59
+ return createForm<FormData, object>({
44
60
  initialData: { country: "", state: "", province: "", region: "" },
45
- initialUiState: { showState: false, showProvince: false },
61
+ initialUiState: {},
46
62
  plugins: [createArbiterPlugin({ rules: arbiterRules })],
47
63
  });
48
64
  }
49
65
 
66
+ function visibility(form: ReturnType<typeof makeForm>) {
67
+ return Object.fromEntries(
68
+ form.getState().fieldPolicy.map((item) => [String(item.path.segments[0]), item.visible]),
69
+ ) as { state: boolean; province: boolean };
70
+ }
71
+
50
72
  describe("demo 18 arbiter visibility — direct state", () => {
51
73
  test("initial state: both hidden", () => {
52
74
  const form = makeForm();
53
- const { uiState } = form.getState();
54
- expect(uiState.showState).toBe(false);
55
- expect(uiState.showProvince).toBe(false);
75
+ form.setValue("country", "");
76
+ expect(visibility(form)).toEqual({ province: false, state: false });
56
77
  form.dispose();
57
78
  });
58
79
 
59
80
  test("select US: showState=true, showProvince=false", () => {
60
81
  const form = makeForm();
61
82
  form.setValue("country", "US");
62
- const { uiState } = form.getState();
63
- expect(uiState.showState).toBe(true);
64
- expect(uiState.showProvince).toBe(false);
83
+ expect(visibility(form)).toEqual({ province: false, state: true });
65
84
  form.dispose();
66
85
  });
67
86
 
@@ -69,9 +88,7 @@ describe("demo 18 arbiter visibility — direct state", () => {
69
88
  const form = makeForm();
70
89
  form.setValue("country", "US");
71
90
  form.setValue("country", "CA");
72
- const { uiState } = form.getState();
73
- expect(uiState.showState).toBe(false);
74
- expect(uiState.showProvince).toBe(true);
91
+ expect(visibility(form)).toEqual({ province: true, state: false });
75
92
  form.dispose();
76
93
  });
77
94
 
@@ -80,9 +97,7 @@ describe("demo 18 arbiter visibility — direct state", () => {
80
97
  form.setValue("country", "US");
81
98
  form.setValue("country", "CA");
82
99
  form.setValue("country", "UK");
83
- const { uiState } = form.getState();
84
- expect(uiState.showState).toBe(false);
85
- expect(uiState.showProvince).toBe(false);
100
+ expect(visibility(form)).toEqual({ province: false, state: false });
86
101
  form.dispose();
87
102
  });
88
103
 
@@ -92,9 +107,7 @@ describe("demo 18 arbiter visibility — direct state", () => {
92
107
  form.setValue("country", "CA");
93
108
  form.setValue("country", "UK");
94
109
  form.setValue("country", "US");
95
- const { uiState } = form.getState();
96
- expect(uiState.showState).toBe(true);
97
- expect(uiState.showProvince).toBe(false);
110
+ expect(visibility(form)).toEqual({ province: false, state: true });
98
111
  form.dispose();
99
112
  });
100
113
 
@@ -102,9 +115,7 @@ describe("demo 18 arbiter visibility — direct state", () => {
102
115
  const form = makeForm();
103
116
  form.setValue("country", "US");
104
117
  form.setValue("country", "");
105
- const { uiState } = form.getState();
106
- expect(uiState.showState).toBe(false);
107
- expect(uiState.showProvince).toBe(false);
118
+ expect(visibility(form)).toEqual({ province: false, state: false });
108
119
  form.dispose();
109
120
  });
110
121
  });
@@ -112,35 +123,30 @@ describe("demo 18 arbiter visibility — direct state", () => {
112
123
  describe("demo 18 arbiter visibility — subscription", () => {
113
124
  test("subscriber sees correct uiState after each setValue", () => {
114
125
  const form = makeForm();
115
- const snapshots: UiState[] = [];
126
+ const snapshots: ReturnType<typeof visibility>[] = [];
116
127
  form.subscribe(() => {
117
- snapshots.push({ ...form.getState().uiState });
128
+ snapshots.push(visibility(form));
118
129
  });
119
130
 
120
131
  form.setValue("country", "US");
121
132
  const afterUS = snapshots[snapshots.length - 1];
122
- expect(afterUS.showState).toBe(true);
123
- expect(afterUS.showProvince).toBe(false);
133
+ expect(afterUS).toEqual({ province: false, state: true });
124
134
 
125
135
  form.setValue("country", "CA");
126
136
  const afterCA = snapshots[snapshots.length - 1];
127
- expect(afterCA.showState).toBe(false);
128
- expect(afterCA.showProvince).toBe(true);
137
+ expect(afterCA).toEqual({ province: true, state: false });
129
138
 
130
139
  form.setValue("country", "UK");
131
140
  const afterUK = snapshots[snapshots.length - 1];
132
- expect(afterUK.showState).toBe(false);
133
- expect(afterUK.showProvince).toBe(false);
141
+ expect(afterUK).toEqual({ province: false, state: false });
134
142
 
135
143
  form.setValue("country", "US");
136
144
  const afterUS2 = snapshots[snapshots.length - 1];
137
- expect(afterUS2.showState).toBe(true);
138
- expect(afterUS2.showProvince).toBe(false);
145
+ expect(afterUS2).toEqual({ province: false, state: true });
139
146
 
140
147
  form.setValue("country", "");
141
148
  const afterEmpty = snapshots[snapshots.length - 1];
142
- expect(afterEmpty.showState).toBe(false);
143
- expect(afterEmpty.showProvince).toBe(false);
149
+ expect(afterEmpty).toEqual({ province: false, state: false });
144
150
 
145
151
  form.dispose();
146
152
  });
@@ -86,7 +86,7 @@ describe("createArbiterPlugin with pre-configured session", () => {
86
86
  expect((state.uiState as Record<string, unknown>).visible).toBe(true);
87
87
  // data writes should pass through
88
88
  expect((state.data as Record<string, unknown>).name).toBe("kept");
89
- // $meta remains internal; Arbiter policy production is intentionally deferred.
89
+ // Legacy $meta writes remain internal and do not produce field policy.
90
90
  expect(state.fieldPolicy).toEqual([]);
91
91
  form.dispose();
92
92
  });
@@ -0,0 +1,215 @@
1
+ import { type ArbiterError, ArbiterErrorCode, createSession } from "@arbitre/core";
2
+ import type { ProductionRule } from "@arbitre/core";
3
+ import { createForm } from "@formbar/core";
4
+ import { describe, expect, test } from "vitest";
5
+ import { createArbiterPlugin } from "../arbiter-plugin.js";
6
+ import { readFieldPolicyOutput } from "../field-policy-output.js";
7
+
8
+ const output = (path: string, policy: Record<string, unknown>) => ({ path, ...policy });
9
+ const source = (record: unknown) => ({ getPath: () => ({ target: record }) });
10
+
11
+ function expectCompilationError(run: () => unknown): void {
12
+ expect(run).toThrowError(
13
+ expect.objectContaining<Partial<ArbiterError>>({ code: ArbiterErrorCode.RULE_COMPILATION_FAILED }),
14
+ );
15
+ }
16
+
17
+ describe("field policy descriptor boundary", () => {
18
+ test("captures every descriptor once and returns fresh frozen validated data", () => {
19
+ const target = { path: "/victim", visible: false, label: "" };
20
+ const descriptorReads = new Map<PropertyKey, number>();
21
+ let getReads = 0;
22
+ const changing = new Proxy(target, {
23
+ get(target, key, receiver) {
24
+ getReads++;
25
+ return Reflect.get(target, key, receiver);
26
+ },
27
+ getOwnPropertyDescriptor(target, key) {
28
+ descriptorReads.set(key, (descriptorReads.get(key) ?? 0) + 1);
29
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
30
+ if (key !== "visible" || !descriptor) return descriptor;
31
+ return { ...descriptor, value: descriptorReads.get(key) === 1 ? false : undefined };
32
+ },
33
+ });
34
+
35
+ const result = readFieldPolicyOutput(source(changing));
36
+ expect(result).toEqual([{ path: "/victim", visible: false, label: "" }]);
37
+ expect(getReads).toBe(0);
38
+ expect([...descriptorReads.values()]).toEqual([1, 1, 1]);
39
+ expect(Object.isFrozen(result)).toBe(true);
40
+ expect(Object.isFrozen(result[0])).toBe(true);
41
+ target.visible = true;
42
+ expect(result[0]?.visible).toBe(false);
43
+ expect(result[0]).not.toBe(changing);
44
+ });
45
+
46
+ test("does not execute a throwing get trap", () => {
47
+ let getReads = 0;
48
+ const throwingGet = new Proxy(output("/safe", { required: true }), {
49
+ get() {
50
+ getReads++;
51
+ throw new Error("get trap must not execute");
52
+ },
53
+ });
54
+ expect(readFieldPolicyOutput(source(throwingGet))).toEqual([{ path: "/safe", required: true }]);
55
+ expect(getReads).toBe(0);
56
+ });
57
+
58
+ test("commits captured values rather than a borrowed session proxy", () => {
59
+ let visibleReads = 0;
60
+ const changing = new Proxy(output("/victim", { visible: false, label: "" }), {
61
+ get(target, key, receiver) {
62
+ if (key !== "visible") return Reflect.get(target, key, receiver);
63
+ visibleReads++;
64
+ return visibleReads === 1 ? false : undefined;
65
+ },
66
+ });
67
+ const session = createSession();
68
+ const form = createForm({ initialData: { tick: 0 }, plugins: [createArbiterPlugin({ session })] });
69
+ session.assert("$formbar.fieldPolicy.proxy", changing);
70
+ expect(form.setValue("tick", 1).ok).toBe(true);
71
+ expect(visibleReads).toBe(0);
72
+ expect(form.getState().fieldPolicy).toMatchObject([
73
+ { path: { namespace: "data", segments: ["victim"] }, producerId: "arbiter", visible: false, label: "" },
74
+ ]);
75
+ form.dispose();
76
+ session.dispose();
77
+ });
78
+
79
+ test("converts ownKeys and descriptor trap failures to Arbiter diagnostics", () => {
80
+ const throwingOwnKeys = new Proxy(output("/name", { visible: true }), {
81
+ ownKeys() {
82
+ throw new Error("ownKeys");
83
+ },
84
+ });
85
+ const throwingDescriptor = new Proxy(output("/name", { visible: true }), {
86
+ getOwnPropertyDescriptor() {
87
+ throw new Error("descriptor");
88
+ },
89
+ });
90
+ expectCompilationError(() => readFieldPolicyOutput(source(throwingOwnKeys)));
91
+ expectCompilationError(() => readFieldPolicyOutput(source(throwingDescriptor)));
92
+ const revoked = Proxy.revocable(output("/name", { visible: true }), {});
93
+ revoked.revoke();
94
+ expectCompilationError(() => readFieldPolicyOutput(source(revoked.proxy)));
95
+ });
96
+
97
+ test("converts a throwing reserved-root get trap to Arbiter diagnostics", () => {
98
+ const session = createSession();
99
+ const throwingRoot = new Proxy(
100
+ { fieldPolicy: {} },
101
+ {
102
+ get() {
103
+ throw new Error("reserved-root-get");
104
+ },
105
+ },
106
+ );
107
+ session.assert("$formbar", throwingRoot);
108
+ expectCompilationError(() => readFieldPolicyOutput(session));
109
+ session.dispose();
110
+ });
111
+
112
+ test("rejects accessors without invoking getters", () => {
113
+ let getterReads = 0;
114
+ const accessor = Object.defineProperty({ path: "/name" }, "visible", {
115
+ enumerable: true,
116
+ get() {
117
+ getterReads++;
118
+ return true;
119
+ },
120
+ });
121
+ expectCompilationError(() => readFieldPolicyOutput(source(accessor)));
122
+ expect(getterReads).toBe(0);
123
+ });
124
+
125
+ test("rejects custom prototypes and unknown properties", () => {
126
+ const custom = Object.assign(Object.create({ inherited: true }), output("/name", { visible: true }));
127
+ expectCompilationError(() => readFieldPolicyOutput(source(custom)));
128
+ expectCompilationError(() => readFieldPolicyOutput(source(output("/name", { visible: true, hidden: true }))));
129
+ });
130
+
131
+ test("retains the prior core snapshot when hostile session state fails", () => {
132
+ const session = createSession();
133
+ const form = createForm({ initialData: { tick: 0 }, plugins: [createArbiterPlugin({ session })] });
134
+ session.assert("$formbar.fieldPolicy.target", output("/name", { visible: false }));
135
+ expect(form.setValue("tick", 1).ok).toBe(true);
136
+ const previous = form.getState().fieldPolicy;
137
+ const hostile = new Proxy(output("/other", { visible: true }), {
138
+ ownKeys() {
139
+ throw new Error("hostile-ownKeys");
140
+ },
141
+ });
142
+ session.assert("$formbar.fieldPolicy.target", hostile);
143
+ const failed = form.setValue("tick", 2);
144
+ expect(failed).toMatchObject({ ok: false, error: "Arbiter session state could not be evaluated safely" });
145
+ expect(form.getState().fieldPolicy).toBe(previous);
146
+ expect((form.getState().data as { tick: number }).tick).toBe(1);
147
+ form.dispose();
148
+ session.dispose();
149
+ });
150
+ });
151
+
152
+ describe("reserved $formbar synchronization boundary", () => {
153
+ const malicious = {
154
+ fieldPolicy: {
155
+ injected: { path: "/victim", visible: false },
156
+ legit: { path: "/overridden", required: false },
157
+ },
158
+ userPayload: "retained-only-in-form-data",
159
+ };
160
+
161
+ test("ignores reserved user data while synchronizing ordinary data and UI", () => {
162
+ const session = createSession();
163
+ const form = createForm({
164
+ initialData: {
165
+ $formbar: malicious,
166
+ "$formbar.fieldPolicy.direct": output("/direct", { visible: false }),
167
+ profile: { name: "Ada" },
168
+ tick: 0,
169
+ },
170
+ initialUiState: { ready: true },
171
+ plugins: [createArbiterPlugin({ session })],
172
+ });
173
+ expect(form.setValue("tick", 1).ok).toBe(true);
174
+ expect(form.getState().fieldPolicy).toEqual([]);
175
+ expect(session.getPath("$formbar")).toBeUndefined();
176
+ expect(session.getPath("profile")).toEqual({ name: "Ada" });
177
+ expect(session.getPath("$ui.ready")).toBe(true);
178
+ form.dispose();
179
+ expect(() => session.assert("hostOwned", true)).not.toThrow();
180
+ session.dispose();
181
+ });
182
+
183
+ test("cannot override or receive rule-owned output and clears it on the next tick", () => {
184
+ const rules: readonly ProductionRule[] = [
185
+ {
186
+ name: "legitimate",
187
+ when: { active: true },
188
+ then: [{ $set: { "$formbar.fieldPolicy.legit": output("/legitimate", { required: true }) } }],
189
+ },
190
+ ];
191
+ const session = createSession({ rules });
192
+ const form = createForm({
193
+ initialData: { $formbar: malicious, active: false, tick: 0 },
194
+ initialUiState: {},
195
+ plugins: [createArbiterPlugin({ session })],
196
+ });
197
+ form.setValue("active", true);
198
+ form.setValue("tick", 1);
199
+ expect(form.getState().fieldPolicy).toMatchObject([
200
+ { path: { namespace: "data", segments: ["legitimate"] }, producerId: "arbiter", required: true },
201
+ ]);
202
+ expect(session.getPath("$formbar.fieldPolicy.injected")).toBeUndefined();
203
+ expect((form.getState().data as Record<string, unknown>).$formbar).toEqual(malicious);
204
+ expect((form.getState().uiState as Record<string, unknown>).$formbar).toBeUndefined();
205
+
206
+ form.reset();
207
+ expect(form.getState().fieldPolicy).toEqual([]);
208
+ form.setValue("tick", 1);
209
+ expect(form.getState().fieldPolicy).toEqual([]);
210
+ expect(session.getPath("$formbar.fieldPolicy.legit")).toBeUndefined();
211
+ form.dispose();
212
+ expect(() => session.assert("stillOwned", true)).not.toThrow();
213
+ session.dispose();
214
+ });
215
+ });