@formbar/arbiter 0.4.0 → 0.7.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.cjs +24 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -50
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/arbiter-integration.test.ts +2 -0
- package/src/arbiter-plugin.ts +32 -78
package/dist/index.cjs
CHANGED
|
@@ -12,60 +12,34 @@ function isArbiterInternalPath(path) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// src/arbiter-plugin.ts
|
|
15
|
+
function resolveSession(options) {
|
|
16
|
+
if (options.session) return { session: options.session, owned: false };
|
|
17
|
+
if (options.rules) return { session: core.createSession({ rules: options.rules }), owned: true };
|
|
18
|
+
throw new Error("createArbiterPlugin requires either `rules` or `session`");
|
|
19
|
+
}
|
|
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]);
|
|
25
|
+
}
|
|
26
|
+
function toWrites(result) {
|
|
27
|
+
return result.changes.filter((change) => !isArbiterInternalPath(change.path)).map((change) => ({ path: change.path, value: change.newValue, mode: "set" }));
|
|
28
|
+
}
|
|
29
|
+
function evaluateSession(session, ctx) {
|
|
30
|
+
if (ctx.origin.startsWith("plugin:arbiter")) return;
|
|
31
|
+
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 };
|
|
35
|
+
}
|
|
15
36
|
function createArbiterPlugin(options) {
|
|
16
|
-
const {
|
|
17
|
-
let session;
|
|
18
|
-
let ownsSession;
|
|
19
|
-
if (externalSession) {
|
|
20
|
-
session = externalSession;
|
|
21
|
-
ownsSession = false;
|
|
22
|
-
} else if (rules) {
|
|
23
|
-
session = core.createSession({ rules });
|
|
24
|
-
ownsSession = true;
|
|
25
|
-
} else {
|
|
26
|
-
throw new Error("createArbiterPlugin requires either `rules` or `session`");
|
|
27
|
-
}
|
|
37
|
+
const { session, owned } = resolveSession(options);
|
|
28
38
|
return {
|
|
29
39
|
id: "arbiter",
|
|
30
|
-
evaluate(ctx)
|
|
31
|
-
if (ctx.origin.startsWith("plugin:arbiter")) return;
|
|
32
|
-
if (!ctx.change.dataChanged && !ctx.change.uiChanged) return;
|
|
33
|
-
const data = ctx.data;
|
|
34
|
-
for (const key of Object.keys(data)) {
|
|
35
|
-
session.assert(key, data[key]);
|
|
36
|
-
}
|
|
37
|
-
const uiState = ctx.uiState;
|
|
38
|
-
for (const key of Object.keys(uiState)) {
|
|
39
|
-
session.assert(`$ui.${key}`, uiState[key]);
|
|
40
|
-
}
|
|
41
|
-
const result = session.fire();
|
|
42
|
-
const writes = [];
|
|
43
|
-
for (const change of result.changes) {
|
|
44
|
-
if (isArbiterInternalPath(change.path)) continue;
|
|
45
|
-
writes.push({
|
|
46
|
-
path: change.path,
|
|
47
|
-
value: change.newValue,
|
|
48
|
-
mode: "set"
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
const fieldMeta = {};
|
|
52
|
-
const state = session.getState();
|
|
53
|
-
for (const [path, value] of Object.entries(state)) {
|
|
54
|
-
if (!path.startsWith("$meta.")) continue;
|
|
55
|
-
const fieldPath = path.slice("$meta.".length);
|
|
56
|
-
if (typeof value === "object" && value !== null) {
|
|
57
|
-
fieldMeta[fieldPath] = value;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return {
|
|
61
|
-
writes: writes.length > 0 ? writes : void 0,
|
|
62
|
-
fieldMeta: Object.keys(fieldMeta).length > 0 ? fieldMeta : void 0
|
|
63
|
-
};
|
|
64
|
-
},
|
|
40
|
+
evaluate: (ctx) => evaluateSession(session, ctx),
|
|
65
41
|
onDispose() {
|
|
66
|
-
if (
|
|
67
|
-
session.dispose();
|
|
68
|
-
}
|
|
42
|
+
if (owned) session.dispose();
|
|
69
43
|
}
|
|
70
44
|
};
|
|
71
45
|
}
|
package/dist/index.cjs.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":["createSession","evaluate","synchronousValue","ExpressionError","createExpressionService"],"mappings":"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","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 {\n\tFormPlugin,\n\tPluginEvaluateContext,\n\tPluginEvaluateResult,\n\tPluginFieldMeta,\n\tPluginWrite,\n} 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\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[] and PluginFieldMeta records.\n */\nexport function createArbiterPlugin(options: ArbiterPluginOptions): FormPlugin {\n\tconst { rules, session: externalSession } = options;\n\n\tlet session: RuleSession;\n\tlet ownsSession: boolean;\n\n\tif (externalSession) {\n\t\tsession = externalSession;\n\t\townsSession = false;\n\t} else if (rules) {\n\t\tsession = createSession({ rules: rules as ProductionRule[] });\n\t\townsSession = true;\n\t} else {\n\t\tthrow new Error(\"createArbiterPlugin requires either `rules` or `session`\");\n\t}\n\n\treturn {\n\t\tid: \"arbiter\",\n\n\t\tevaluate(ctx: PluginEvaluateContext): PluginEvaluateResult | undefined {\n\t\t\t// Prevent re-entry from own writes\n\t\t\tif (ctx.origin.startsWith(\"plugin:arbiter\")) return;\n\n\t\t\t// Short-circuit when nothing relevant changed\n\t\t\tif (!ctx.change.dataChanged && !ctx.change.uiChanged) return;\n\n\t\t\t// Sync form data fields into the session\n\t\t\tconst data = ctx.data as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(data)) {\n\t\t\t\tsession.assert(key, data[key]);\n\t\t\t}\n\n\t\t\t// Sync $ui.* state\n\t\t\tconst uiState = ctx.uiState as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(uiState)) {\n\t\t\t\tsession.assert(`$ui.${key}`, uiState[key]);\n\t\t\t}\n\n\t\t\t// Fire rules\n\t\t\tconst result: FiringResult = session.fire();\n\n\t\t\t// Convert changes to PluginWrite[], filtering internal paths\n\t\t\tconst writes: PluginWrite[] = [];\n\t\t\tfor (const change of result.changes) {\n\t\t\t\tif (isArbiterInternalPath(change.path)) continue;\n\t\t\t\twrites.push({\n\t\t\t\t\tpath: change.path,\n\t\t\t\t\tvalue: change.newValue,\n\t\t\t\t\tmode: \"set\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Derive field meta from session state\n\t\t\tconst fieldMeta: Record<string, PluginFieldMeta> = {};\n\t\t\tconst state = session.getState();\n\t\t\tfor (const [path, value] of Object.entries(state)) {\n\t\t\t\tif (!path.startsWith(\"$meta.\")) continue;\n\t\t\t\t// Convention: $meta.<fieldPath> holds { visible, disabled, required, readOnly, label }\n\t\t\t\tconst fieldPath = path.slice(\"$meta.\".length);\n\t\t\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\t\t\tfieldMeta[fieldPath] = value as PluginFieldMeta;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twrites: writes.length > 0 ? writes : undefined,\n\t\t\t\tfieldMeta: Object.keys(fieldMeta).length > 0 ? fieldMeta : undefined,\n\t\t\t};\n\t\t},\n\n\t\tonDispose() {\n\t\t\tif (ownsSession) {\n\t\t\t\tsession.dispose();\n\t\t\t}\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/internal-paths.ts","../src/arbiter-plugin.ts","../src/expression-utils.ts","../src/expression-then-operator.ts"],"names":["createSession","evaluate","synchronousValue","ExpressionError","createExpressionService"],"mappings":"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","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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -13,7 +13,7 @@ interface ArbiterPluginOptions {
|
|
|
13
13
|
/**
|
|
14
14
|
* Creates a FormPlugin that bridges @arbitre/core into the formbar pipeline.
|
|
15
15
|
* Syncs form data into the rule session, fires rules, and converts results
|
|
16
|
-
* into PluginWrite[]
|
|
16
|
+
* into PluginWrite[] records.
|
|
17
17
|
*/
|
|
18
18
|
declare function createArbiterPlugin(options: ArbiterPluginOptions): FormPlugin;
|
|
19
19
|
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface ArbiterPluginOptions {
|
|
|
13
13
|
/**
|
|
14
14
|
* Creates a FormPlugin that bridges @arbitre/core into the formbar pipeline.
|
|
15
15
|
* Syncs form data into the rule session, fires rules, and converts results
|
|
16
|
-
* into PluginWrite[]
|
|
16
|
+
* into PluginWrite[] records.
|
|
17
17
|
*/
|
|
18
18
|
declare function createArbiterPlugin(options: ArbiterPluginOptions): FormPlugin;
|
|
19
19
|
|
package/dist/index.js
CHANGED
|
@@ -11,60 +11,34 @@ function isArbiterInternalPath(path) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// src/arbiter-plugin.ts
|
|
14
|
+
function resolveSession(options) {
|
|
15
|
+
if (options.session) return { session: options.session, owned: false };
|
|
16
|
+
if (options.rules) return { session: createSession({ rules: options.rules }), owned: true };
|
|
17
|
+
throw new Error("createArbiterPlugin requires either `rules` or `session`");
|
|
18
|
+
}
|
|
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]);
|
|
24
|
+
}
|
|
25
|
+
function toWrites(result) {
|
|
26
|
+
return result.changes.filter((change) => !isArbiterInternalPath(change.path)).map((change) => ({ path: change.path, value: change.newValue, mode: "set" }));
|
|
27
|
+
}
|
|
28
|
+
function evaluateSession(session, ctx) {
|
|
29
|
+
if (ctx.origin.startsWith("plugin:arbiter")) return;
|
|
30
|
+
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 };
|
|
34
|
+
}
|
|
14
35
|
function createArbiterPlugin(options) {
|
|
15
|
-
const {
|
|
16
|
-
let session;
|
|
17
|
-
let ownsSession;
|
|
18
|
-
if (externalSession) {
|
|
19
|
-
session = externalSession;
|
|
20
|
-
ownsSession = false;
|
|
21
|
-
} else if (rules) {
|
|
22
|
-
session = createSession({ rules });
|
|
23
|
-
ownsSession = true;
|
|
24
|
-
} else {
|
|
25
|
-
throw new Error("createArbiterPlugin requires either `rules` or `session`");
|
|
26
|
-
}
|
|
36
|
+
const { session, owned } = resolveSession(options);
|
|
27
37
|
return {
|
|
28
38
|
id: "arbiter",
|
|
29
|
-
evaluate(ctx)
|
|
30
|
-
if (ctx.origin.startsWith("plugin:arbiter")) return;
|
|
31
|
-
if (!ctx.change.dataChanged && !ctx.change.uiChanged) return;
|
|
32
|
-
const data = ctx.data;
|
|
33
|
-
for (const key of Object.keys(data)) {
|
|
34
|
-
session.assert(key, data[key]);
|
|
35
|
-
}
|
|
36
|
-
const uiState = ctx.uiState;
|
|
37
|
-
for (const key of Object.keys(uiState)) {
|
|
38
|
-
session.assert(`$ui.${key}`, uiState[key]);
|
|
39
|
-
}
|
|
40
|
-
const result = session.fire();
|
|
41
|
-
const writes = [];
|
|
42
|
-
for (const change of result.changes) {
|
|
43
|
-
if (isArbiterInternalPath(change.path)) continue;
|
|
44
|
-
writes.push({
|
|
45
|
-
path: change.path,
|
|
46
|
-
value: change.newValue,
|
|
47
|
-
mode: "set"
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
const fieldMeta = {};
|
|
51
|
-
const state = session.getState();
|
|
52
|
-
for (const [path, value] of Object.entries(state)) {
|
|
53
|
-
if (!path.startsWith("$meta.")) continue;
|
|
54
|
-
const fieldPath = path.slice("$meta.".length);
|
|
55
|
-
if (typeof value === "object" && value !== null) {
|
|
56
|
-
fieldMeta[fieldPath] = value;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
writes: writes.length > 0 ? writes : void 0,
|
|
61
|
-
fieldMeta: Object.keys(fieldMeta).length > 0 ? fieldMeta : void 0
|
|
62
|
-
};
|
|
63
|
-
},
|
|
39
|
+
evaluate: (ctx) => evaluateSession(session, ctx),
|
|
64
40
|
onDispose() {
|
|
65
|
-
if (
|
|
66
|
-
session.dispose();
|
|
67
|
-
}
|
|
41
|
+
if (owned) session.dispose();
|
|
68
42
|
}
|
|
69
43
|
};
|
|
70
44
|
}
|
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 {\n\tFormPlugin,\n\tPluginEvaluateContext,\n\tPluginEvaluateResult,\n\tPluginFieldMeta,\n\tPluginWrite,\n} 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\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[] and PluginFieldMeta records.\n */\nexport function createArbiterPlugin(options: ArbiterPluginOptions): FormPlugin {\n\tconst { rules, session: externalSession } = options;\n\n\tlet session: RuleSession;\n\tlet ownsSession: boolean;\n\n\tif (externalSession) {\n\t\tsession = externalSession;\n\t\townsSession = false;\n\t} else if (rules) {\n\t\tsession = createSession({ rules: rules as ProductionRule[] });\n\t\townsSession = true;\n\t} else {\n\t\tthrow new Error(\"createArbiterPlugin requires either `rules` or `session`\");\n\t}\n\n\treturn {\n\t\tid: \"arbiter\",\n\n\t\tevaluate(ctx: PluginEvaluateContext): PluginEvaluateResult | undefined {\n\t\t\t// Prevent re-entry from own writes\n\t\t\tif (ctx.origin.startsWith(\"plugin:arbiter\")) return;\n\n\t\t\t// Short-circuit when nothing relevant changed\n\t\t\tif (!ctx.change.dataChanged && !ctx.change.uiChanged) return;\n\n\t\t\t// Sync form data fields into the session\n\t\t\tconst data = ctx.data as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(data)) {\n\t\t\t\tsession.assert(key, data[key]);\n\t\t\t}\n\n\t\t\t// Sync $ui.* state\n\t\t\tconst uiState = ctx.uiState as Record<string, unknown>;\n\t\t\tfor (const key of Object.keys(uiState)) {\n\t\t\t\tsession.assert(`$ui.${key}`, uiState[key]);\n\t\t\t}\n\n\t\t\t// Fire rules\n\t\t\tconst result: FiringResult = session.fire();\n\n\t\t\t// Convert changes to PluginWrite[], filtering internal paths\n\t\t\tconst writes: PluginWrite[] = [];\n\t\t\tfor (const change of result.changes) {\n\t\t\t\tif (isArbiterInternalPath(change.path)) continue;\n\t\t\t\twrites.push({\n\t\t\t\t\tpath: change.path,\n\t\t\t\t\tvalue: change.newValue,\n\t\t\t\t\tmode: \"set\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Derive field meta from session state\n\t\t\tconst fieldMeta: Record<string, PluginFieldMeta> = {};\n\t\t\tconst state = session.getState();\n\t\t\tfor (const [path, value] of Object.entries(state)) {\n\t\t\t\tif (!path.startsWith(\"$meta.\")) continue;\n\t\t\t\t// Convention: $meta.<fieldPath> holds { visible, disabled, required, readOnly, label }\n\t\t\t\tconst fieldPath = path.slice(\"$meta.\".length);\n\t\t\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\t\t\tfieldMeta[fieldPath] = value as PluginFieldMeta;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twrites: writes.length > 0 ? writes : undefined,\n\t\t\t\tfieldMeta: Object.keys(fieldMeta).length > 0 ? fieldMeta : undefined,\n\t\t\t};\n\t\t},\n\n\t\tonDispose() {\n\t\t\tif (ownsSession) {\n\t\t\t\tsession.dispose();\n\t\t\t}\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/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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formbar/arbiter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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.
|
|
31
|
+
"@formbar/core": "^0.7.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@arbitre/core": "^0.3.0",
|
|
@@ -86,6 +86,8 @@ 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.
|
|
90
|
+
expect(state.fieldPolicy).toEqual([]);
|
|
89
91
|
form.dispose();
|
|
90
92
|
});
|
|
91
93
|
});
|
package/src/arbiter-plugin.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { createSession } from "@arbitre/core";
|
|
2
2
|
import type { FiringResult, ProductionRule, RuleSession } from "@arbitre/core";
|
|
3
|
-
import type {
|
|
4
|
-
FormPlugin,
|
|
5
|
-
PluginEvaluateContext,
|
|
6
|
-
PluginEvaluateResult,
|
|
7
|
-
PluginFieldMeta,
|
|
8
|
-
PluginWrite,
|
|
9
|
-
} from "@formbar/core";
|
|
3
|
+
import type { FormPlugin, PluginEvaluateContext, PluginEvaluateResult, PluginWrite } from "@formbar/core";
|
|
10
4
|
import { isArbiterInternalPath } from "./internal-paths.js";
|
|
11
5
|
|
|
12
6
|
export interface ArbiterPluginOptions {
|
|
@@ -16,85 +10,45 @@ export interface ArbiterPluginOptions {
|
|
|
16
10
|
readonly session?: RuleSession;
|
|
17
11
|
}
|
|
18
12
|
|
|
13
|
+
function resolveSession(options: ArbiterPluginOptions): { session: RuleSession; owned: boolean } {
|
|
14
|
+
if (options.session) return { session: options.session, owned: false };
|
|
15
|
+
if (options.rules) return { session: createSession({ rules: options.rules as ProductionRule[] }), owned: true };
|
|
16
|
+
throw new Error("createArbiterPlugin requires either `rules` or `session`");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function syncSession(session: RuleSession, ctx: PluginEvaluateContext): void {
|
|
20
|
+
const data = ctx.data as Record<string, unknown>;
|
|
21
|
+
for (const key of Object.keys(data)) session.assert(key, data[key]);
|
|
22
|
+
const uiState = ctx.uiState as Record<string, unknown>;
|
|
23
|
+
for (const key of Object.keys(uiState)) session.assert(`$ui.${key}`, uiState[key]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function toWrites(result: FiringResult): readonly PluginWrite[] {
|
|
27
|
+
return result.changes
|
|
28
|
+
.filter((change) => !isArbiterInternalPath(change.path))
|
|
29
|
+
.map((change) => ({ path: change.path, value: change.newValue, mode: "set" as const }));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function evaluateSession(session: RuleSession, ctx: PluginEvaluateContext): PluginEvaluateResult | undefined {
|
|
33
|
+
if (ctx.origin.startsWith("plugin:arbiter")) return;
|
|
34
|
+
if (!ctx.change.dataChanged && !ctx.change.uiChanged) return;
|
|
35
|
+
syncSession(session, ctx);
|
|
36
|
+
const writes = toWrites(session.fire());
|
|
37
|
+
return { writes: writes.length > 0 ? writes : undefined };
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
/**
|
|
20
41
|
* Creates a FormPlugin that bridges @arbitre/core into the formbar pipeline.
|
|
21
42
|
* Syncs form data into the rule session, fires rules, and converts results
|
|
22
|
-
* into PluginWrite[]
|
|
43
|
+
* into PluginWrite[] records.
|
|
23
44
|
*/
|
|
24
45
|
export function createArbiterPlugin(options: ArbiterPluginOptions): FormPlugin {
|
|
25
|
-
const {
|
|
26
|
-
|
|
27
|
-
let session: RuleSession;
|
|
28
|
-
let ownsSession: boolean;
|
|
29
|
-
|
|
30
|
-
if (externalSession) {
|
|
31
|
-
session = externalSession;
|
|
32
|
-
ownsSession = false;
|
|
33
|
-
} else if (rules) {
|
|
34
|
-
session = createSession({ rules: rules as ProductionRule[] });
|
|
35
|
-
ownsSession = true;
|
|
36
|
-
} else {
|
|
37
|
-
throw new Error("createArbiterPlugin requires either `rules` or `session`");
|
|
38
|
-
}
|
|
39
|
-
|
|
46
|
+
const { session, owned } = resolveSession(options);
|
|
40
47
|
return {
|
|
41
48
|
id: "arbiter",
|
|
42
|
-
|
|
43
|
-
evaluate(ctx: PluginEvaluateContext): PluginEvaluateResult | undefined {
|
|
44
|
-
// Prevent re-entry from own writes
|
|
45
|
-
if (ctx.origin.startsWith("plugin:arbiter")) return;
|
|
46
|
-
|
|
47
|
-
// Short-circuit when nothing relevant changed
|
|
48
|
-
if (!ctx.change.dataChanged && !ctx.change.uiChanged) return;
|
|
49
|
-
|
|
50
|
-
// Sync form data fields into the session
|
|
51
|
-
const data = ctx.data as Record<string, unknown>;
|
|
52
|
-
for (const key of Object.keys(data)) {
|
|
53
|
-
session.assert(key, data[key]);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Sync $ui.* state
|
|
57
|
-
const uiState = ctx.uiState as Record<string, unknown>;
|
|
58
|
-
for (const key of Object.keys(uiState)) {
|
|
59
|
-
session.assert(`$ui.${key}`, uiState[key]);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Fire rules
|
|
63
|
-
const result: FiringResult = session.fire();
|
|
64
|
-
|
|
65
|
-
// Convert changes to PluginWrite[], filtering internal paths
|
|
66
|
-
const writes: PluginWrite[] = [];
|
|
67
|
-
for (const change of result.changes) {
|
|
68
|
-
if (isArbiterInternalPath(change.path)) continue;
|
|
69
|
-
writes.push({
|
|
70
|
-
path: change.path,
|
|
71
|
-
value: change.newValue,
|
|
72
|
-
mode: "set",
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Derive field meta from session state
|
|
77
|
-
const fieldMeta: Record<string, PluginFieldMeta> = {};
|
|
78
|
-
const state = session.getState();
|
|
79
|
-
for (const [path, value] of Object.entries(state)) {
|
|
80
|
-
if (!path.startsWith("$meta.")) continue;
|
|
81
|
-
// Convention: $meta.<fieldPath> holds { visible, disabled, required, readOnly, label }
|
|
82
|
-
const fieldPath = path.slice("$meta.".length);
|
|
83
|
-
if (typeof value === "object" && value !== null) {
|
|
84
|
-
fieldMeta[fieldPath] = value as PluginFieldMeta;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
writes: writes.length > 0 ? writes : undefined,
|
|
90
|
-
fieldMeta: Object.keys(fieldMeta).length > 0 ? fieldMeta : undefined,
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
|
|
49
|
+
evaluate: (ctx) => evaluateSession(session, ctx),
|
|
94
50
|
onDispose() {
|
|
95
|
-
if (
|
|
96
|
-
session.dispose();
|
|
97
|
-
}
|
|
51
|
+
if (owned) session.dispose();
|
|
98
52
|
},
|
|
99
53
|
};
|
|
100
54
|
}
|