@zvenigora/ng-eval-forms 0.1.0 → 0.2.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"zvenigora-ng-eval-forms-reactive.mjs","sources":["../../../../modules/eval-forms/reactive/src/lib/control-source.ts","../../../../modules/eval-forms/reactive/src/lib/field-schema.ts","../../../../modules/eval-forms/reactive/src/zvenigora-ng-eval-forms-reactive.ts"],"sourcesContent":["import { DestroyRef, Injector, Signal } from '@angular/core';\r\nimport { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';\r\nimport type { AbstractControl, FormGroup } from '@angular/forms';\r\nimport type { SignalContextSource } from '@zvenigora/ng-eval-signals';\r\nimport { Subject, startWith, switchMap, takeUntil } from 'rxjs';\r\n\r\n/**\r\n * One key's mirror: the control it currently points at, the channel that\r\n * re-points it, and the one that ends it.\r\n *\r\n * Both channels exist because `toSignal(obs, { injector })` takes its cleanup\r\n * from that injector's `DestroyRef` and hands back no per-subscription handle.\r\n * Releasing *one* control's subscription while the injector is still alive\r\n * therefore has to happen inside the stream, and the two cases need different\r\n * operators:\r\n *\r\n * - **replaced** - `instances` emits, `switchMap` drops the previous inner\r\n * subscription and takes the new one. One release, one subscribe.\r\n * - **removed** - `released` emits and `takeUntil` unsubscribes the chain.\r\n * Completing `instances` is *not* enough and the difference is not academic:\r\n * `switchMap` completes only once its inner completes too, and\r\n * `valueChanges` never does - so the dead control keeps its subscriber. This\r\n * is measured, not reasoned; the removal spec fails against the version that\r\n * only completed `instances`.\r\n */\r\ninterface ControlChannel {\r\n control: AbstractControl;\r\n instances: Subject<AbstractControl>;\r\n released: Subject<void>;\r\n}\r\n\r\n/**\r\n * Builds the form-wide source of an expression context from a `FormGroup`.\r\n *\r\n * One mirror **per control, never per group** (plan S 3.5.1). A disabled\r\n * control is excluded from its parent's aggregate value and there is no\r\n * `rawValueChanges` to go with `getRawValue()`, so a group-backed mirror would\r\n * lose a field the moment anything disabled it - and this library computes\r\n * disablement, so one field's rule would silently blank the value every *other*\r\n * field's rules resolve against. A control's own `valueChanges` keeps emitting\r\n * regardless of its enabled state.\r\n *\r\n * **The record holds live values behind accessors, not the mirrored signals**\r\n * (S 3.5.5). Each key is an enumerable accessor that reads its change-signal -\r\n * which is what a surrounding `computed()` records as a dependency, per key -\r\n * and then answers with the control's *current* value. Reading the control\r\n * rather than the signal is what makes `invalidate()` a working hatch for\r\n * trap 3: a `{ emitEvent: false }` write leaves the change-signal stale, and a\r\n * record that handed out that signal would have nothing to recover, however\r\n * often it was re-evaluated.\r\n *\r\n * **One entry per key, for the life of that key** (S 3.5.5). A replaced control\r\n * instance re-points its channel; it does not get a new accessor or a new\r\n * signal. Building fresh ones would leave every property that had already read\r\n * the key tracking the *dead* control's signal - frozen at its last value, for\r\n * the life of the binding, which is the failure trap 5 exists to prevent\r\n * reappearing one layer up.\r\n *\r\n * **The returned record must be passed by reference, never spread or cloned.**\r\n * `{ ...source }` flattens the accessors to the values they happened to hold,\r\n * so the copy records no dependency and every property over it freezes at its\r\n * first value with no error. The type - `Record<string, unknown>` - gives no\r\n * hint of this, and phase-3-plan S 9.2 describes composing sources by\r\n * spreading, which is why it is said here.\r\n *\r\n * **`invalidate()` covers a stale *value*, not a suppressed *control-set*\r\n * change.** Trap 3's hatch works because the accessor re-reads the control, so\r\n * re-running the walk recovers. `setControl` / `addControl` / `removeControl`\r\n * with `{ emitEvent: false }` suppress `group.events` instead, so `sync` never\r\n * runs and the channel still points at the dead instance - there is nothing\r\n * for a re-run to recover, in the same structural sense that rules out a\r\n * signal-valued record. Current behaviour, pinned here rather than fixed:\r\n * the observable is the only signal there is.\r\n *\r\n * @param group - A flat `FormGroup`. Nested groups and `FormArray` are out of\r\n * scope for this phase.\r\n * @param options - `injector` scopes every subscription here to the form's\r\n * lifetime; it is required rather than optional because an\r\n * explicit injector means no `DestroyRef` auto-teardown, and\r\n * an optional one would silently vary that (plan S 3.7).\r\n */\r\nexport const createControlSource = (\r\n group: FormGroup,\r\n options: { injector: Injector }\r\n): SignalContextSource => {\r\n\r\n const source: SignalContextSource = {};\r\n const channels = new Map<string, ControlChannel>();\r\n\r\n // `FormGroup`'s default type argument is `any`, so its `controls` is too.\r\n // Narrowed once here rather than at four call sites.\r\n const controls = (): Record<string, AbstractControl> =>\r\n group.controls as Record<string, AbstractControl>;\r\n\r\n const open = (name: string, control: AbstractControl): void => {\r\n\r\n const channel: ControlChannel = {\r\n control,\r\n instances: new Subject<AbstractControl>(),\r\n released: new Subject<void>(),\r\n };\r\n\r\n // `startWith` twice, and both are load-bearing. The outer one subscribes\r\n // the control the key opened with. The inner one replays the *current*\r\n // instance's value on subscribe, so a replacement produces an emission\r\n // immediately instead of waiting for its first edit - without it a\r\n // property reading this key would sit on the dead control's value until\r\n // someone typed into the new one.\r\n //\r\n // The signal's own value is never read; it is a change ticker. It still\r\n // has to carry values, because `toSignal` dedupes with `Object.is` and a\r\n // stream emitting a constant would tick once and never again.\r\n const changed: Signal<unknown> = toSignal(\r\n channel.instances.pipe(\r\n startWith(control),\r\n switchMap((instance) =>\r\n instance.valueChanges.pipe(startWith(instance.value))\r\n ),\r\n takeUntil(channel.released)\r\n ),\r\n { injector: options.injector, requireSync: true }\r\n );\r\n\r\n Object.defineProperty(source, name, {\r\n get: () => {\r\n changed();\r\n return channel.control.value;\r\n },\r\n enumerable: true,\r\n configurable: true,\r\n });\r\n\r\n // Registered last, after everything that can throw. `toSignal` can\r\n // (NG0205 from a destroyed injector, NG0601 from a stream that does not\r\n // emit synchronously), and a channel registered before it would leave\r\n // `channels` holding a key `source` has no accessor for - which `sync`\r\n // then reads as \"unchanged\" forever, so the key is permanently\r\n // un-mirrored with no path back.\r\n channels.set(name, channel);\r\n };\r\n\r\n const close = (name: string, channel: ControlChannel): void => {\r\n channel.released.next();\r\n channel.released.complete();\r\n channel.instances.complete();\r\n channels.delete(name);\r\n delete source[name];\r\n };\r\n\r\n // Diffs the key -> instance map against the group's current controls\r\n // (S 3.5.5). Deleting the entry being visited is safe: `Map` iteration\r\n // tolerates it.\r\n const sync = (): void => {\r\n\r\n const live = controls();\r\n\r\n for (const [name, channel] of channels) {\r\n // Own-property, never a bare `live[name]` (plan S 3.4.2's rule, applied\r\n // on this side of the library too). `FormGroup` keeps the caller's\r\n // object literal and rejects only keys containing a dot, so a\r\n // server-supplied field named `constructor` or `toString` is legal -\r\n // and after its control is removed a bare read resolves the\r\n // *prototype's* value instead of `undefined`. This loop would then take\r\n // the \"replaced\" branch, push `Object` down the channel, and throw out\r\n // of the `group.events` subscriber - which unsubscribes it, silently\r\n // ending all diffing for the life of the form.\r\n const replacement: AbstractControl | undefined =\r\n Object.prototype.hasOwnProperty.call(live, name)\r\n ? live[name]\r\n : undefined;\r\n\r\n if (replacement === channel.control) {\r\n continue;\r\n }\r\n\r\n if (replacement === undefined) {\r\n close(name, channel);\r\n continue;\r\n }\r\n\r\n channel.control = replacement;\r\n channel.instances.next(replacement);\r\n }\r\n\r\n for (const [name, control] of Object.entries(live)) {\r\n if (!channels.has(name)) {\r\n open(name, control);\r\n }\r\n }\r\n };\r\n\r\n for (const [name, control] of Object.entries(controls())) {\r\n open(name, control);\r\n }\r\n\r\n // After the channels, deliberately: with the injector missing - which the\r\n // type forbids but a JavaScript caller can still do - `toSignal` above\r\n // throws NG0203 naming the injection context, where reading `DestroyRef` off\r\n // `undefined` first would throw a `TypeError` naming nothing.\r\n //\r\n // `group.events` and not `valueChanges`: `setControl` / `addControl` /\r\n // `removeControl` all reach `updateValueAndValidity`, so the control set\r\n // changing is observable here.\r\n //\r\n // It fires on ordinary interaction too, and on more than one event apiece -\r\n // value, status, pristine, touched and reset are all `ControlEvent`s - so\r\n // this runs several O(N) diffs per interaction rather than one. That is the\r\n // price of not handing the consumer a `rebind()` they will forget to call,\r\n // and there is no narrower signal available: Angular's collection-change\r\n // callback is private. Nothing per-node is touched, so eval-core's\r\n // performance gate is unaffected.\r\n group.events\r\n .pipe(takeUntilDestroyed(options.injector.get(DestroyRef)))\r\n .subscribe(() => sync());\r\n\r\n return source;\r\n};\r\n","import {\r\n DestroyRef,\r\n EnvironmentInjector,\r\n Injector,\r\n computed,\r\n createEnvironmentInjector,\r\n inject,\r\n} from '@angular/core';\r\nimport { AbstractControl, FormControl, FormGroup } from '@angular/forms';\r\nimport {\r\n ExpressionErrorPolicy,\r\n createFieldContext,\r\n toText,\r\n toVisible,\r\n} from '@zvenigora/ng-eval-forms';\r\nimport { EvalSignal, createEvalSignal } from '@zvenigora/ng-eval-signals';\r\nimport { createControlSource } from './control-source';\r\n\r\n/**\r\n * One field's rules, as strings resolved at runtime.\r\n *\r\n * This is the whole descriptor. It is deliberately not a schema *language*:\r\n * `visible` and `text` are the two properties this phase ships (plan S 3.6),\r\n * `disabled` is deferred with three reasons rather than a shrug, and\r\n * `required` is deferred with the validators.\r\n */\r\nexport interface FieldSchema {\r\n\r\n /**\r\n * The field's name, and the key its control is mirrored under.\r\n *\r\n * It does not have to name a control: a rule naming a field the group has\r\n * no control for resolves `undefined`, which is S 3.4.3's ordinary case\r\n * rather than an error. It may not be a member of `Object.prototype` -\r\n * see {@link bindFieldProperties}.\r\n */\r\n readonly name: string;\r\n\r\n /** An expression whose value is coerced by truthiness (`toVisible`). */\r\n readonly visible?: string;\r\n\r\n /** An expression whose value is coerced to a string (`toText`). */\r\n readonly text?: string;\r\n}\r\n\r\n/**\r\n * The signals bound for one field - one per rule the schema supplied.\r\n *\r\n * Typed as `EvalSignal`, not `Signal`, and both extra members are load-bearing\r\n * on this path (plan S 5): `invalidate()` is trap 3's hatch for a\r\n * `{ emitEvent: false }` write and S 3.4.2's hatch for a key-set change, and\r\n * `destroy()` is what step 5's teardown counts. Typing these as `Signal` would\r\n * put both out of reach through the published surface.\r\n *\r\n * The type arguments are `boolean` and `string` rather than S 5's original\r\n * `unknown`, **because the coercion of S 3.6 happens here**. That section was\r\n * written before the coercion's placement was settled and `coercion.ts` states\r\n * the settlement - \"both adapters call this\" - so `unknown` would now describe\r\n * the value the property is built *from* rather than the one it answers with.\r\n */\r\nexport interface FieldProperties {\r\n readonly visible?: EvalSignal<boolean>;\r\n readonly text?: EvalSignal<string>;\r\n}\r\n\r\n/**\r\n * What {@link bindFieldProperties} returns: the bound fields, and the one call\r\n * that ends them.\r\n *\r\n * **Nested rather than a `destroy` written onto the record** (plan S 5's step-5\r\n * amendment). The record is keyed by *field name*, `destroy` is a legal field\r\n * name, and S 0's premise is that those names arrive from a server rather than\r\n * from the consumer - so a flat shape would put a silent collision between the\r\n * consumer's data and this library's API in precisely the case where the\r\n * consumer controls the names least.\r\n */\r\nexport interface FormBinding {\r\n\r\n /** The bound properties, keyed by field name. */\r\n readonly fields: Record<string, FieldProperties>;\r\n\r\n /**\r\n * Destroys every `EvalSignal` this binding created and releases the mirror.\r\n *\r\n * **Call it.** Every signal here is built with an explicit injector, and\r\n * that means no `DestroyRef` registration of its own (`eval-signal.ts`) - so\r\n * nothing auto-destroys and all N x M are the caller's (plan S 3.7).\r\n *\r\n * There is one net under that, and it is a net rather than a substitute: the\r\n * binding registers this on the `DestroyRef` of the injector it was given,\r\n * so a binding wired to a component or route injector is released when that\r\n * injector dies even if nobody called it. It is not a licence to skip the\r\n * call - a binding built on the root injector is released at the end of the\r\n * application and no sooner.\r\n *\r\n * Idempotent, and that is load-bearing rather than polite: the scope is an\r\n * `EnvironmentInjector`, and `R3Injector.destroy()` throws NG0205 when it\r\n * has already run.\r\n */\r\n destroy(): void;\r\n}\r\n\r\n/**\r\n * Puts a coercion in front of an `EvalSignal` without losing the two members\r\n * that make it one.\r\n *\r\n * A second `computed` rather than a coercion inside the walk: the expression\r\n * is the consumer's and its *value* is what the error policy and the\r\n * dependency tracking are defined over, so the coercion belongs strictly\r\n * downstream of both. It is also what keeps a destroyed signal - which reads\r\n * `undefined` - answering `false` / `''` rather than leaking `undefined` into\r\n * a template.\r\n *\r\n * `Object.assign` then `defineProperty`, mirroring `createEvalSignal`'s own\r\n * construction: `assign` copies a getter's *current value*, which would freeze\r\n * `dependencies` at the empty set it holds before the first recompute.\r\n */\r\nconst coerce = <T>(\r\n inner: EvalSignal<unknown>,\r\n map: (value: unknown) => T\r\n): EvalSignal<T> => {\r\n\r\n const outer = Object.assign(computed(() => map(inner())), {\r\n invalidate: () => inner.invalidate(),\r\n destroy: () => inner.destroy(),\r\n }) as EvalSignal<T>;\r\n\r\n Object.defineProperty(outer, 'dependencies', {\r\n get: () => inner.dependencies,\r\n enumerable: true,\r\n configurable: true,\r\n });\r\n\r\n return outer;\r\n};\r\n\r\n/**\r\n * The three checks of open question 8.3, plus 8.5's half.\r\n *\r\n * Three checks, and the count is the boundary rather than a starting point.\r\n * Each catches something that is otherwise either silent or reported far from\r\n * its cause, and none of them is a rule about what an *expression* may say -\r\n * that is `eval-core`'s and is already answered.\r\n */\r\nconst validate = (schema: readonly FieldSchema[], group: FormGroup): void => {\r\n\r\n const seen = new Set<string>();\r\n\r\n for (const field of schema) {\r\n\r\n if (seen.has(field.name)) {\r\n throw new Error(\r\n `Duplicate field name '${field.name}' in the schema: ` +\r\n `each field is bound once, so the second would silently replace the first.`\r\n );\r\n }\r\n seen.add(field.name);\r\n\r\n // The check no other layer can make (S 3.4.3's third precedence layer).\r\n // A server-supplied field named `constructor`, `toString`, `valueOf` or\r\n // `__proto__` is legal to `FormGroup`, which rejects only keys containing\r\n // a dot - and when no control backs it, `EvalContext.get` consults\r\n // `original` first and `getContextValue` reads a plain object as a bare\r\n // property access, so the name resolves off `Object.prototype`. Every rule\r\n // reading it then gets a *function*, which is truthy: `visible` renders\r\n // precisely the field that has no data, with no error anywhere.\r\n //\r\n // It is not containable downstream - the resolution happens above this\r\n // library, in `createSignalContext`'s `original`, and S 2 forbids the\r\n // upstream fix - and this is the only layer that can name the offending\r\n // field in the message.\r\n if (Object.prototype.hasOwnProperty.call(Object.prototype, field.name)) {\r\n throw new Error(\r\n `Field name '${field.name}' is a member of Object.prototype and cannot be ` +\r\n `resolved reliably: an expression naming it reads the prototype's value ` +\r\n `whenever no control backs it. Rename the field.`\r\n );\r\n }\r\n\r\n for (const property of ['visible', 'text'] as const) {\r\n const rule = field[property];\r\n\r\n if (rule !== undefined && typeof rule !== 'string') {\r\n throw new Error(\r\n `Field '${field.name}': ${property} must be a string expression, ` +\r\n `not ${typeof rule}.`\r\n );\r\n }\r\n }\r\n }\r\n\r\n // Checked over the *group*, not over the schema's names, because the mirror\r\n // covers every control (S 3.5) - so a control nobody named still reaches\r\n // every field's context, where an expression finds it.\r\n //\r\n // Construction-time only, and that is a limitation rather than a guarantee:\r\n // `addControl` afterwards reaches `sync` -> `open` with no check, and\r\n // throwing from there is not available - a throw inside the `group.events`\r\n // subscriber unsubscribes it and silently ends all diffing for the life of\r\n // the form (S 3.5.5). The README says so.\r\n for (const [name, control] of Object.entries(\r\n group.controls as Record<string, AbstractControl>\r\n )) {\r\n // The same check as above, over the other source of names rather than a\r\n // fourth one - and the mirror does **not** rescue this case, which is\r\n // easy to believe and wrong. The mirror does define an own accessor for\r\n // `constructor` on its record, but the record is reached through\r\n // `lookups`, step 4 of `EvalContext.get`, while `original` - the empty\r\n // field source - is read at step 2 with a bare property access. Measured:\r\n // the accessor is present and the expression still reads\r\n // `function Object() { [native code] }`. So a control named off\r\n // `Object.prototype` is unreadable through every expression, silently and\r\n // truthily, and worse than the schema case because the value exists.\r\n if (Object.prototype.hasOwnProperty.call(Object.prototype, name)) {\r\n throw new Error(\r\n `Control '${name}' is a member of Object.prototype and cannot be read by any ` +\r\n `expression: the name resolves to the prototype's value before the form is ` +\r\n `ever consulted. Rename the control.`\r\n );\r\n }\r\n\r\n // Flat forms only (S 2, open question 8.5): a thrown error rather than a\r\n // documented limitation, because the alternative surfaces as a confusing\r\n // evaluation result far from its cause - a nested group's *aggregate\r\n // object* arriving where a value was expected.\r\n if (!(control instanceof FormControl)) {\r\n throw new Error(\r\n `Control '${name}' is not a FormControl. Nested groups and FormArrays are ` +\r\n `out of scope for this phase.`\r\n );\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Binds a schema to a `FormGroup`, producing one `EvalSignal` per rule.\r\n *\r\n * ```ts\r\n * const binding = bindFieldProperties(\r\n * [{ name: 'state', visible: \"country === 'US'\" }],\r\n * form,\r\n * { injector }\r\n * );\r\n *\r\n * binding.fields['state'].visible(); // recomputes when `country` changes\r\n *\r\n * binding.destroy(); // yours to call - nothing here\r\n * // auto-destroys (S 3.7)\r\n * ```\r\n *\r\n * **One `EvalContext` per field, never one shared** (plan S 3.4.1). `get`\r\n * resolves `scopes` and `original` *before* `lookups`, and\r\n * `arrow-function-expression.ts` pushes its scope with no `try`/`finally` - so\r\n * one field's arrow function that throws leaves a scope on the context for the\r\n * life of the form, and under a shared context it would shadow every other\r\n * field's key of the same name. The count is N contexts for N fields, and not\r\n * N x M: the properties of one field resolve against the same names.\r\n *\r\n * **The field half of each context is empty in this phase.**\r\n * `createFieldContext` takes two sources and the second is `{}` here. What a\r\n * field-local key set should *contain* is specified nowhere - S 3.4.3's\r\n * \"a field named `value`, `name` or `index`\" illustrates collision semantics\r\n * rather than naming keys - and inventing three keys to fill a parameter is\r\n * how a public surface acquires members nobody chose. The consequence, stated\r\n * rather than left to be found: S 3.4.3's precedence rule is asserted at the\r\n * core in `field-context.spec.ts` and ships **untested end to end**, because\r\n * no `/reactive` path produces a field-local key. It gets settled by whichever\r\n * phase first has a consumer for one.\r\n *\r\n * **`createControlSource` is called once, for the whole form.** Per field\r\n * would build N mirrors over one group: N x the live `toSignal` subscriptions\r\n * step 5 counts, and trap 5's O(N) diff running N times per `group.events`\r\n * emission, on a signal that already fires several times per interaction.\r\n *\r\n * **Constructed, not computed.** `toSignal` opens with\r\n * `assertNotInReactiveContext`, so calling this from inside an `effect()` or a\r\n * `computed()` throws out of the mirror with an error naming `toSignal` and\r\n * nothing naming this library. A form binding belongs in a service or a\r\n * factory, which is the same premise `injector` being required rests on.\r\n *\r\n * **Teardown runs off a child injector, and there is only one of them**\r\n * (S 3.7, amended in step 5). The binding opens an `EnvironmentInjector` under\r\n * the caller's and scopes *both* the mirror and every `createEvalSignal` to it,\r\n * so `destroy()` releases the whole mirror - every per-key `toSignal`\r\n * subscription and the `group.events` one - in a single call, and a bind that\r\n * throws part-way through releases exactly the same thing however far it got.\r\n * The plan's earlier premise, that releasing the mirror needed a handle\r\n * `createControlSource` does not return, was wrong: step 3 had already measured\r\n * that destroying the injector drops every control's observer count to 0 and\r\n * ends the diffing.\r\n *\r\n * The `EvalSignal`s are **not** covered by that, and that is the point of the\r\n * count: built with an explicit injector, they take no `DestroyRef`\r\n * registration, so `destroy()` walks them itself and N x M of them is what a\r\n * spec asserts rather than \"the form's destroy ran\".\r\n *\r\n * @param schema - The fields to bind. Validated at construction: a duplicate\r\n * name, a non-string rule, a name that resolves off\r\n * `Object.prototype`, and a control that is not a\r\n * `FormControl` all throw here rather than surfacing later as\r\n * an evaluation result nobody can trace.\r\n * @param group - A flat `FormGroup` of `FormControl`s.\r\n * @param options - `injector` is **required**, not optional: S 3.7's argument\r\n * is that every `EvalSignal` here has no auto-teardown, and\r\n * an optional injector would silently vary that for a call\r\n * that happened to sit in an injection context. `onError`\r\n * defaults to `'undefined'` - the opposite of\r\n * `eval-signals`' default, and resolved *here* rather than\r\n * forwarded absent, since `createEvalSignal` would otherwise\r\n * supply `'throw'` (S 3.4.4).\r\n * @returns The bound properties keyed by field name, and the `destroy()` that\r\n * ends them.\r\n */\r\nexport const bindFieldProperties = (\r\n schema: readonly FieldSchema[],\r\n group: FormGroup,\r\n options: { injector: Injector; onError?: ExpressionErrorPolicy }\r\n): FormBinding => {\r\n\r\n // Before the mirror, deliberately. A schema rejection then opens no\r\n // subscription at all, which is a stronger statement than \"the catch below\r\n // releases it\" and is asserted separately.\r\n validate(schema, group);\r\n\r\n // `?.` and the `inject` fallback are not defensive dressing: the type makes\r\n // `injector` required, but a JavaScript caller can omit it, and reading\r\n // `.get` off `undefined` is a `TypeError` naming nothing. `toSignal` solves\r\n // the same problem the same way (`rxjs-interop.mjs:106`), and it is what\r\n // keeps the error a caller sees NG0203 - naming the injection context -\r\n // rather than a property read on `undefined`. `control-source.ts` orders\r\n // its own statements for that error; this preserves it one layer up.\r\n const parent =\r\n options?.injector?.get(EnvironmentInjector) ?? inject(EnvironmentInjector);\r\n\r\n // The other arm of the same problem, and the fallback above is what opens\r\n // it: *inside* an injection context that fallback succeeds, so a caller who\r\n // omitted the required `injector` would get a working binding parented at\r\n // the ambient environment injector - with no auto-teardown and no\r\n // diagnostic. That is the exact silent variation S 3.7 makes `injector`\r\n // required to prevent, so it is rejected here by name rather than left to\r\n // surface as a leak. Outside a context the line above has already thrown\r\n // NG0203 and this is unreachable.\r\n if (!options?.injector) {\r\n throw new Error(\r\n `bindFieldProperties requires an 'injector': every signal it creates is built ` +\r\n `with one and therefore takes no DestroyRef registration, so an ambient ` +\r\n `injection context would silently vary when teardown runs.`\r\n );\r\n }\r\n\r\n const scope = createEnvironmentInjector([], parent);\r\n\r\n const onError = options.onError ?? 'undefined';\r\n\r\n const fields: Record<string, FieldProperties> = {};\r\n\r\n // The signals, in creation order, so the `catch` below releases exactly what\r\n // exists rather than walking `fields` - which holds nothing for the field\r\n // that threw and nothing at all for a throw in `createControlSource`.\r\n const created: EvalSignal<unknown>[] = [];\r\n\r\n let destroyed = false;\r\n\r\n // The registration on the *caller's* injector, held so `destroy()` can drop\r\n // it. See below for why it exists at all.\r\n let unregister: (() => void) | undefined;\r\n\r\n const destroy = (): void => {\r\n\r\n if (destroyed) {\r\n return;\r\n }\r\n\r\n destroyed = true;\r\n\r\n // Cleared before it is called, so a teardown driven *by* the caller's\r\n // injector does not turn around and mutate the hook list that injector is\r\n // iterating - `eval-signal.ts:408-414`'s pattern and its reason.\r\n const release = unregister;\r\n unregister = undefined;\r\n release?.();\r\n\r\n try {\r\n // Called through the property rather than a captured reference: it is\r\n // the object the caller holds, and `coerce`'s `destroy` delegates to the\r\n // inner signal, so this is the one call that reaches both.\r\n for (const signal of created) {\r\n signal.destroy();\r\n }\r\n } finally {\r\n // In a `finally` because `destroyed` is already set: a signal whose\r\n // `destroy` throws - a consumer calling this from inside a `computed()`\r\n // reaches NG0600 - would otherwise leave the scope alive with the retry\r\n // guarded into a no-op, and every mirror subscription live with no way\r\n // back.\r\n //\r\n // Last, and idempotence is why the guard above exists at all:\r\n // `R3Injector.destroy()` opens with `assertNotDestroyed` and throws\r\n // NG0205 on a second call.\r\n scope.destroy();\r\n }\r\n };\r\n\r\n // **The scope is detached, not merely shorter-lived.**\r\n // `createEnvironmentInjector` does not register the child with its parent's\r\n // destroy hooks, so without this the mirror would outlive the injector the\r\n // caller scoped it to - and step 4, which passed that injector to\r\n // `createControlSource` directly, released everything when it died. A\r\n // consumer who wires a binding to a component or route injector and forgets\r\n // `destroy()` would otherwise retain N subscriptions, the mirror, the N\r\n // contexts and the form itself for the life of the root injector, silently.\r\n // `destroy()` stays the documented call; this is the net under it.\r\n unregister = options.injector.get(DestroyRef).onDestroy(() => {\r\n unregister = undefined;\r\n destroy();\r\n });\r\n\r\n try {\r\n\r\n // Scoped to the child, not to the caller's injector: it is what makes the\r\n // mirror's two subscriptions releasable at all without a handle.\r\n const formSource = createControlSource(group, { injector: scope });\r\n\r\n for (const field of schema) {\r\n\r\n const context = createFieldContext(formSource, {});\r\n const properties: { visible?: EvalSignal<boolean>; text?: EvalSignal<string> } = {};\r\n\r\n // `options.injector`, **not** the scope, and the distinction is not\r\n // cosmetic: `createEvalSignal` uses the injector it is given only to\r\n // resolve `CompilerService` (`eval-signal.ts:215-216`) - it registers no\r\n // teardown against it - and the scope's parent is\r\n // `caller.get(EnvironmentInjector)`, which for a node injector is an\r\n // *ancestor*. Passing the scope would therefore skip providers the\r\n // caller declared, silently, and diverge from step 4 for no gain. The\r\n // scope exists for the one thing that does need a releasable lifetime,\r\n // which is the mirror.\r\n if (field.visible !== undefined) {\r\n properties.visible = coerce(\r\n createEvalSignal(field.visible, context, {\r\n injector: options.injector,\r\n onError,\r\n }),\r\n toVisible\r\n );\r\n created.push(properties.visible);\r\n }\r\n\r\n if (field.text !== undefined) {\r\n properties.text = coerce(\r\n createEvalSignal(field.text, context, {\r\n injector: options.injector,\r\n onError,\r\n }),\r\n toText\r\n );\r\n created.push(properties.text);\r\n }\r\n\r\n fields[field.name] = properties;\r\n }\r\n\r\n } catch (error) {\r\n // A *parse* error is the reachable case (S 3.4.4's amendment):\r\n // `createEvalSignal` compiles eagerly, so `visible: 'country ==='` throws\r\n // from inside this loop with the mirror already open - and the caller\r\n // holds no handle to any of it, because the binding never returned.\r\n destroy();\r\n throw error;\r\n }\r\n\r\n return { fields, destroy };\r\n};\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"zvenigora-ng-eval-forms-reactive.mjs","sources":["../../../../modules/eval-forms/reactive/src/lib/control-source.ts","../../../../modules/eval-forms/reactive/src/lib/field-schema.ts","../../../../modules/eval-forms/reactive/src/zvenigora-ng-eval-forms-reactive.ts"],"sourcesContent":["import { DestroyRef, Injector, Signal } from '@angular/core';\r\nimport { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';\r\nimport type { AbstractControl, FormGroup } from '@angular/forms';\r\nimport type { SignalContextSource } from '@zvenigora/ng-eval-signals';\r\nimport { Subject, startWith, switchMap, takeUntil } from 'rxjs';\r\n\r\n/**\r\n * One key's mirror: the control it currently points at, the channel that\r\n * re-points it, and the one that ends it.\r\n *\r\n * Both channels exist because `toSignal(obs, { injector })` takes its cleanup\r\n * from that injector's `DestroyRef` and hands back no per-subscription handle.\r\n * Releasing *one* control's subscription while the injector is still alive\r\n * therefore has to happen inside the stream, and the two cases need different\r\n * operators:\r\n *\r\n * - **replaced** - `instances` emits, `switchMap` drops the previous inner\r\n * subscription and takes the new one. One release, one subscribe.\r\n * - **removed** - `released` emits and `takeUntil` unsubscribes the chain.\r\n * Completing `instances` is *not* enough and the difference is not academic:\r\n * `switchMap` completes only once its inner completes too, and\r\n * `valueChanges` never does - so the dead control keeps its subscriber. This\r\n * is measured, not reasoned; the removal spec fails against the version that\r\n * only completed `instances`.\r\n */\r\ninterface ControlChannel {\r\n control: AbstractControl;\r\n instances: Subject<AbstractControl>;\r\n released: Subject<void>;\r\n}\r\n\r\n/**\r\n * Builds the form-wide source of an expression context from a `FormGroup`.\r\n *\r\n * One mirror **per control, never per group** (plan S 3.5.1). A disabled\r\n * control is excluded from its parent's aggregate value and there is no\r\n * `rawValueChanges` to go with `getRawValue()`, so a group-backed mirror would\r\n * lose a field the moment anything disabled it - and this library computes\r\n * disablement, so one field's rule would silently blank the value every *other*\r\n * field's rules resolve against. A control's own `valueChanges` keeps emitting\r\n * regardless of its enabled state.\r\n *\r\n * **The record holds live values behind accessors, not the mirrored signals**\r\n * (S 3.5.5). Each key is an enumerable accessor that reads its change-signal -\r\n * which is what a surrounding `computed()` records as a dependency, per key -\r\n * and then answers with the control's *current* value. Reading the control\r\n * rather than the signal is what makes `invalidate()` a working hatch for\r\n * trap 3: a `{ emitEvent: false }` write leaves the change-signal stale, and a\r\n * record that handed out that signal would have nothing to recover, however\r\n * often it was re-evaluated.\r\n *\r\n * **One entry per key, for the life of that key** (S 3.5.5). A replaced control\r\n * instance re-points its channel; it does not get a new accessor or a new\r\n * signal. Building fresh ones would leave every property that had already read\r\n * the key tracking the *dead* control's signal - frozen at its last value, for\r\n * the life of the binding, which is the failure trap 5 exists to prevent\r\n * reappearing one layer up.\r\n *\r\n * **The returned record must be passed by reference, never spread or cloned.**\r\n * `{ ...source }` flattens the accessors to the values they happened to hold,\r\n * so the copy records no dependency and every property over it freezes at its\r\n * first value with no error. The type - `Record<string, unknown>` - gives no\r\n * hint of this, and phase-3-plan S 9.2 describes composing sources by\r\n * spreading, which is why it is said here.\r\n *\r\n * **`invalidate()` covers a stale *value*, not a suppressed *control-set*\r\n * change.** Trap 3's hatch works because the accessor re-reads the control, so\r\n * re-running the walk recovers. `setControl` / `addControl` / `removeControl`\r\n * with `{ emitEvent: false }` suppress `group.events` instead, so `sync` never\r\n * runs and the channel still points at the dead instance - there is nothing\r\n * for a re-run to recover, in the same structural sense that rules out a\r\n * signal-valued record. Current behaviour, pinned here rather than fixed:\r\n * the observable is the only signal there is.\r\n *\r\n * @param group - A flat `FormGroup`. Nested groups and `FormArray` are out of\r\n * scope for this phase.\r\n * @param options - `injector` scopes every subscription here to the form's\r\n * lifetime; it is required rather than optional because an\r\n * explicit injector means no `DestroyRef` auto-teardown, and\r\n * an optional one would silently vary that (plan S 3.7).\r\n */\r\nexport const createControlSource = (\r\n group: FormGroup,\r\n options: { injector: Injector }\r\n): SignalContextSource => {\r\n\r\n const source: SignalContextSource = {};\r\n const channels = new Map<string, ControlChannel>();\r\n\r\n // `FormGroup`'s default type argument is `any`, so its `controls` is too.\r\n // Narrowed once here rather than at four call sites.\r\n const controls = (): Record<string, AbstractControl> =>\r\n group.controls as Record<string, AbstractControl>;\r\n\r\n const open = (name: string, control: AbstractControl): void => {\r\n\r\n const channel: ControlChannel = {\r\n control,\r\n instances: new Subject<AbstractControl>(),\r\n released: new Subject<void>(),\r\n };\r\n\r\n // `startWith` twice, and both are load-bearing. The outer one subscribes\r\n // the control the key opened with. The inner one replays the *current*\r\n // instance's value on subscribe, so a replacement produces an emission\r\n // immediately instead of waiting for its first edit - without it a\r\n // property reading this key would sit on the dead control's value until\r\n // someone typed into the new one.\r\n //\r\n // The signal's own value is never read; it is a change ticker. It still\r\n // has to carry values, because `toSignal` dedupes with `Object.is` and a\r\n // stream emitting a constant would tick once and never again.\r\n const changed: Signal<unknown> = toSignal(\r\n channel.instances.pipe(\r\n startWith(control),\r\n switchMap((instance) =>\r\n instance.valueChanges.pipe(startWith(instance.value))\r\n ),\r\n takeUntil(channel.released)\r\n ),\r\n { injector: options.injector, requireSync: true }\r\n );\r\n\r\n Object.defineProperty(source, name, {\r\n get: () => {\r\n changed();\r\n return channel.control.value;\r\n },\r\n enumerable: true,\r\n configurable: true,\r\n });\r\n\r\n // Registered last, after everything that can throw. `toSignal` can\r\n // (NG0205 from a destroyed injector, NG0601 from a stream that does not\r\n // emit synchronously), and a channel registered before it would leave\r\n // `channels` holding a key `source` has no accessor for - which `sync`\r\n // then reads as \"unchanged\" forever, so the key is permanently\r\n // un-mirrored with no path back.\r\n channels.set(name, channel);\r\n };\r\n\r\n const close = (name: string, channel: ControlChannel): void => {\r\n channel.released.next();\r\n channel.released.complete();\r\n channel.instances.complete();\r\n channels.delete(name);\r\n delete source[name];\r\n };\r\n\r\n // Diffs the key -> instance map against the group's current controls\r\n // (S 3.5.5). Deleting the entry being visited is safe: `Map` iteration\r\n // tolerates it.\r\n const sync = (): void => {\r\n\r\n const live = controls();\r\n\r\n for (const [name, channel] of channels) {\r\n // Own-property, never a bare `live[name]` (plan S 3.4.2's rule, applied\r\n // on this side of the library too). `FormGroup` keeps the caller's\r\n // object literal and rejects only keys containing a dot, so a\r\n // server-supplied field named `constructor` or `toString` is legal -\r\n // and after its control is removed a bare read resolves the\r\n // *prototype's* value instead of `undefined`. This loop would then take\r\n // the \"replaced\" branch, push `Object` down the channel, and throw out\r\n // of the `group.events` subscriber - which unsubscribes it, silently\r\n // ending all diffing for the life of the form.\r\n const replacement: AbstractControl | undefined =\r\n Object.prototype.hasOwnProperty.call(live, name)\r\n ? live[name]\r\n : undefined;\r\n\r\n if (replacement === channel.control) {\r\n continue;\r\n }\r\n\r\n if (replacement === undefined) {\r\n close(name, channel);\r\n continue;\r\n }\r\n\r\n channel.control = replacement;\r\n channel.instances.next(replacement);\r\n }\r\n\r\n for (const [name, control] of Object.entries(live)) {\r\n if (!channels.has(name)) {\r\n open(name, control);\r\n }\r\n }\r\n };\r\n\r\n for (const [name, control] of Object.entries(controls())) {\r\n open(name, control);\r\n }\r\n\r\n // After the channels, deliberately: with the injector missing - which the\r\n // type forbids but a JavaScript caller can still do - `toSignal` above\r\n // throws NG0203 naming the injection context, where reading `DestroyRef` off\r\n // `undefined` first would throw a `TypeError` naming nothing.\r\n //\r\n // `group.events` and not `valueChanges`: `setControl` / `addControl` /\r\n // `removeControl` all reach `updateValueAndValidity`, so the control set\r\n // changing is observable here.\r\n //\r\n // It fires on ordinary interaction too, and on more than one event apiece -\r\n // value, status, pristine, touched and reset are all `ControlEvent`s - so\r\n // this runs several O(N) diffs per interaction rather than one. That is the\r\n // price of not handing the consumer a `rebind()` they will forget to call,\r\n // and there is no narrower signal available: Angular's collection-change\r\n // callback is private. Nothing per-node is touched, so eval-core's\r\n // performance gate is unaffected.\r\n group.events\r\n .pipe(takeUntilDestroyed(options.injector.get(DestroyRef)))\r\n .subscribe(() => sync());\r\n\r\n return source;\r\n};\r\n","import {\r\n DestroyRef,\r\n EnvironmentInjector,\r\n Injector,\r\n computed,\r\n createEnvironmentInjector,\r\n inject,\r\n} from '@angular/core';\r\nimport { AbstractControl, FormControl, FormGroup } from '@angular/forms';\r\nimport {\r\n ExpressionErrorPolicy,\r\n createFieldContext,\r\n toText,\r\n toVisible,\r\n} from '@zvenigora/ng-eval-forms';\r\nimport { EvalSignal, createEvalSignal } from '@zvenigora/ng-eval-signals';\r\nimport { createControlSource } from './control-source';\r\n\r\n/**\r\n * One field's rules, as strings resolved at runtime.\r\n *\r\n * This is the whole descriptor. It is deliberately not a schema *language*:\r\n * `visible` and `text` are the two properties this phase ships (plan S 3.6),\r\n * `disabled` is deferred with three reasons rather than a shrug, and\r\n * `required` is deferred with the validators.\r\n */\r\nexport interface FieldSchema {\r\n\r\n /**\r\n * The field's name, and the key its control is mirrored under.\r\n *\r\n * It does not have to name a control: a rule naming a field the group has\r\n * no control for resolves `undefined`, which is S 3.4.3's ordinary case\r\n * rather than an error. It may not be a member of `Object.prototype` -\r\n * see {@link bindFieldProperties}.\r\n */\r\n readonly name: string;\r\n\r\n /** An expression whose value is coerced by truthiness (`toVisible`). */\r\n readonly visible?: string;\r\n\r\n /** An expression whose value is coerced to a string (`toText`). */\r\n readonly text?: string;\r\n}\r\n\r\n/**\r\n * The signals bound for one field - one per rule the schema supplied.\r\n *\r\n * Typed as `EvalSignal`, not `Signal`, and both extra members are load-bearing\r\n * on this path (plan S 5): `invalidate()` is trap 3's hatch for a\r\n * `{ emitEvent: false }` write and S 3.4.2's hatch for a key-set change, and\r\n * `destroy()` is what step 5's teardown counts. Typing these as `Signal` would\r\n * put both out of reach through the published surface.\r\n *\r\n * The type arguments are `boolean` and `string` rather than S 5's original\r\n * `unknown`, **because the coercion of S 3.6 happens here**. That section was\r\n * written before the coercion's placement was settled and `coercion.ts` states\r\n * the settlement - \"both adapters call this\" - so `unknown` would now describe\r\n * the value the property is built *from* rather than the one it answers with.\r\n */\r\nexport interface FieldProperties {\r\n readonly visible?: EvalSignal<boolean>;\r\n readonly text?: EvalSignal<string>;\r\n}\r\n\r\n/**\r\n * What {@link bindFieldProperties} returns: the bound fields, and the one call\r\n * that ends them.\r\n *\r\n * **Nested rather than a `destroy` written onto the record** (plan S 5's step-5\r\n * amendment). The record is keyed by *field name*, `destroy` is a legal field\r\n * name, and S 0's premise is that those names arrive from a server rather than\r\n * from the consumer - so a flat shape would put a silent collision between the\r\n * consumer's data and this library's API in precisely the case where the\r\n * consumer controls the names least.\r\n */\r\nexport interface FormBinding {\r\n\r\n /** The bound properties, keyed by field name. */\r\n readonly fields: Record<string, FieldProperties>;\r\n\r\n /**\r\n * Destroys every `EvalSignal` this binding created and releases the mirror.\r\n *\r\n * **Call it.** Every signal here is built with an explicit injector, and\r\n * that means no `DestroyRef` registration of its own (`eval-signal.ts`) - so\r\n * nothing auto-destroys and all N x M are the caller's (plan S 3.7).\r\n *\r\n * There is one net under that, and it is a net rather than a substitute: the\r\n * binding registers this on the `DestroyRef` of the injector it was given,\r\n * so a binding wired to a component or route injector is released when that\r\n * injector dies even if nobody called it. It is not a licence to skip the\r\n * call - a binding built on the root injector is released at the end of the\r\n * application and no sooner.\r\n *\r\n * Idempotent, and that is load-bearing rather than polite: the scope is an\r\n * `EnvironmentInjector`, and `R3Injector.destroy()` throws NG0205 when it\r\n * has already run.\r\n */\r\n destroy(): void;\r\n}\r\n\r\n/**\r\n * Puts a coercion in front of an `EvalSignal` without losing the two members\r\n * that make it one.\r\n *\r\n * A second `computed` rather than a coercion inside the walk: the expression\r\n * is the consumer's and its *value* is what the error policy and the\r\n * dependency tracking are defined over, so the coercion belongs strictly\r\n * downstream of both. It is also what keeps a destroyed signal - which reads\r\n * `undefined` - answering `false` / `''` rather than leaking `undefined` into\r\n * a template.\r\n *\r\n * `Object.assign` then `defineProperty`, mirroring `createEvalSignal`'s own\r\n * construction: `assign` copies a getter's *current value*, which would freeze\r\n * `dependencies` at the empty set it holds before the first recompute.\r\n */\r\nconst coerce = <T>(\r\n inner: EvalSignal<unknown>,\r\n map: (value: unknown) => T\r\n): EvalSignal<T> => {\r\n\r\n const outer = Object.assign(computed(() => map(inner())), {\r\n invalidate: () => inner.invalidate(),\r\n destroy: () => inner.destroy(),\r\n }) as EvalSignal<T>;\r\n\r\n Object.defineProperty(outer, 'dependencies', {\r\n get: () => inner.dependencies,\r\n enumerable: true,\r\n configurable: true,\r\n });\r\n\r\n return outer;\r\n};\r\n\r\n/**\r\n * The three checks of open question 8.3, plus 8.5's half.\r\n *\r\n * Three checks, and the count is the boundary rather than a starting point.\r\n * Each catches something that is otherwise either silent or reported far from\r\n * its cause, and none of them is a rule about what an *expression* may say -\r\n * that is `eval-core`'s and is already answered.\r\n */\r\nconst validate = (schema: readonly FieldSchema[], group: FormGroup): void => {\r\n\r\n const seen = new Set<string>();\r\n\r\n for (const field of schema) {\r\n\r\n if (seen.has(field.name)) {\r\n throw new Error(\r\n `Duplicate field name '${field.name}' in the schema: ` +\r\n `each field is bound once, so the second would silently replace the first.`\r\n );\r\n }\r\n seen.add(field.name);\r\n\r\n // The check no other layer can make (S 3.4.3's third precedence layer).\r\n // A server-supplied field named `constructor`, `toString`, `valueOf` or\r\n // `__proto__` is legal to `FormGroup`, which rejects only keys containing\r\n // a dot - and when no control backs it, `EvalContext.get` consults\r\n // `original` first and `getContextValue` reads a plain object as a bare\r\n // property access, so the name resolves off `Object.prototype`. Every rule\r\n // reading it then gets a *function*, which is truthy: `visible` renders\r\n // precisely the field that has no data, with no error anywhere.\r\n //\r\n // It is not containable downstream - the resolution happens above this\r\n // library, in `createSignalContext`'s `original`, and S 2 forbids the\r\n // upstream fix - and this is the only layer that can name the offending\r\n // field in the message.\r\n if (Object.prototype.hasOwnProperty.call(Object.prototype, field.name)) {\r\n throw new Error(\r\n `Field name '${field.name}' is a member of Object.prototype and cannot be ` +\r\n `resolved reliably: an expression naming it reads the prototype's value ` +\r\n `whenever no control backs it. Rename the field.`\r\n );\r\n }\r\n\r\n for (const property of ['visible', 'text'] as const) {\r\n const rule = field[property];\r\n\r\n if (rule !== undefined && typeof rule !== 'string') {\r\n throw new Error(\r\n `Field '${field.name}': ${property} must be a string expression, ` +\r\n `not ${typeof rule}.`\r\n );\r\n }\r\n }\r\n }\r\n\r\n // Checked over the *group*, not over the schema's names, because the mirror\r\n // covers every control (S 3.5) - so a control nobody named still reaches\r\n // every field's context, where an expression finds it.\r\n //\r\n // Construction-time only, and that is a limitation rather than a guarantee:\r\n // `addControl` afterwards reaches `sync` -> `open` with no check, and\r\n // throwing from there is not available - a throw inside the `group.events`\r\n // subscriber unsubscribes it and silently ends all diffing for the life of\r\n // the form (S 3.5.5). The README says so.\r\n for (const [name, control] of Object.entries(\r\n group.controls as Record<string, AbstractControl>\r\n )) {\r\n // The same check as above, over the other source of names rather than a\r\n // fourth one - and the mirror does **not** rescue this case, which is\r\n // easy to believe and wrong. The mirror does define an own accessor for\r\n // `constructor` on its record, but the record is reached through\r\n // `lookups`, step 4 of `EvalContext.get`, while `original` - the empty\r\n // field source - is read at step 2 with a bare property access. Measured:\r\n // the accessor is present and the expression still reads\r\n // `function Object() { [native code] }`. So a control named off\r\n // `Object.prototype` is unreadable through every expression, silently and\r\n // truthily, and worse than the schema case because the value exists.\r\n if (Object.prototype.hasOwnProperty.call(Object.prototype, name)) {\r\n throw new Error(\r\n `Control '${name}' is a member of Object.prototype and cannot be read by any ` +\r\n `expression: the name resolves to the prototype's value before the form is ` +\r\n `ever consulted. Rename the control.`\r\n );\r\n }\r\n\r\n // Flat forms only (S 2, open question 8.5): a thrown error rather than a\r\n // documented limitation, because the alternative surfaces as a confusing\r\n // evaluation result far from its cause - a nested group's *aggregate\r\n // object* arriving where a value was expected.\r\n if (!(control instanceof FormControl)) {\r\n throw new Error(\r\n `Control '${name}' is not a FormControl. Nested groups and FormArrays are ` +\r\n `out of scope for this phase.`\r\n );\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Binds a schema to a `FormGroup`, producing one `EvalSignal` per rule.\r\n *\r\n * ```ts\r\n * const binding = bindFieldProperties(\r\n * [{ name: 'state', visible: \"country === 'US'\" }],\r\n * form,\r\n * { injector }\r\n * );\r\n *\r\n * binding.fields['state'].visible(); // recomputes when `country` changes\r\n *\r\n * binding.destroy(); // yours to call - nothing here\r\n * // auto-destroys (S 3.7)\r\n * ```\r\n *\r\n * **One `EvalContext` per field, never one shared** (plan S 3.4.1). `get`\r\n * resolves `scopes` and `original` *before* `lookups`, and an arrow function's\r\n * parameter scope sits on the context for as long as its body runs - so under\r\n * a shared context one field's arrow would shadow every other field's key of\r\n * the same name for that window, and anything that strands a scope rather than\r\n * popping it would do so for the life of the form. The count is N contexts for\r\n * N fields, and not N x M: the properties of one field resolve against the\r\n * same names.\r\n *\r\n * **The field half of each context is empty in this phase.**\r\n * `createFieldContext` takes two sources and the second is `{}` here. What a\r\n * field-local key set should *contain* is specified nowhere - S 3.4.3's\r\n * \"a field named `value`, `name` or `index`\" illustrates collision semantics\r\n * rather than naming keys - and inventing three keys to fill a parameter is\r\n * how a public surface acquires members nobody chose. The consequence, stated\r\n * rather than left to be found: S 3.4.3's precedence rule is asserted at the\r\n * core in `field-context.spec.ts` and ships **untested end to end**, because\r\n * no `/reactive` path produces a field-local key. It gets settled by whichever\r\n * phase first has a consumer for one.\r\n *\r\n * **`createControlSource` is called once, for the whole form.** Per field\r\n * would build N mirrors over one group: N x the live `toSignal` subscriptions\r\n * step 5 counts, and trap 5's O(N) diff running N times per `group.events`\r\n * emission, on a signal that already fires several times per interaction.\r\n *\r\n * **Constructed, not computed.** `toSignal` opens with\r\n * `assertNotInReactiveContext`, so calling this from inside an `effect()` or a\r\n * `computed()` throws out of the mirror with an error naming `toSignal` and\r\n * nothing naming this library. A form binding belongs in a service or a\r\n * factory, which is the same premise `injector` being required rests on.\r\n *\r\n * **Teardown runs off a child injector, and there is only one of them**\r\n * (S 3.7, amended in step 5). The binding opens an `EnvironmentInjector` under\r\n * the caller's and scopes *both* the mirror and every `createEvalSignal` to it,\r\n * so `destroy()` releases the whole mirror - every per-key `toSignal`\r\n * subscription and the `group.events` one - in a single call, and a bind that\r\n * throws part-way through releases exactly the same thing however far it got.\r\n * The plan's earlier premise, that releasing the mirror needed a handle\r\n * `createControlSource` does not return, was wrong: step 3 had already measured\r\n * that destroying the injector drops every control's observer count to 0 and\r\n * ends the diffing.\r\n *\r\n * The `EvalSignal`s are **not** covered by that, and that is the point of the\r\n * count: built with an explicit injector, they take no `DestroyRef`\r\n * registration, so `destroy()` walks them itself and N x M of them is what a\r\n * spec asserts rather than \"the form's destroy ran\".\r\n *\r\n * @param schema - The fields to bind. Validated at construction: a duplicate\r\n * name, a non-string rule, a name that resolves off\r\n * `Object.prototype`, and a control that is not a\r\n * `FormControl` all throw here rather than surfacing later as\r\n * an evaluation result nobody can trace.\r\n * @param group - A flat `FormGroup` of `FormControl`s.\r\n * @param options - `injector` is **required**, not optional: S 3.7's argument\r\n * is that every `EvalSignal` here has no auto-teardown, and\r\n * an optional injector would silently vary that for a call\r\n * that happened to sit in an injection context. `onError`\r\n * defaults to `'undefined'` - the opposite of\r\n * `eval-signals`' default, and resolved *here* rather than\r\n * forwarded absent, since `createEvalSignal` would otherwise\r\n * supply `'throw'` (S 3.4.4).\r\n * @returns The bound properties keyed by field name, and the `destroy()` that\r\n * ends them.\r\n */\r\nexport const bindFieldProperties = (\r\n schema: readonly FieldSchema[],\r\n group: FormGroup,\r\n options: { injector: Injector; onError?: ExpressionErrorPolicy }\r\n): FormBinding => {\r\n\r\n // Before the mirror, deliberately. A schema rejection then opens no\r\n // subscription at all, which is a stronger statement than \"the catch below\r\n // releases it\" and is asserted separately.\r\n validate(schema, group);\r\n\r\n // `?.` and the `inject` fallback are not defensive dressing: the type makes\r\n // `injector` required, but a JavaScript caller can omit it, and reading\r\n // `.get` off `undefined` is a `TypeError` naming nothing. `toSignal` solves\r\n // the same problem the same way (`rxjs-interop.mjs:106`), and it is what\r\n // keeps the error a caller sees NG0203 - naming the injection context -\r\n // rather than a property read on `undefined`. `control-source.ts` orders\r\n // its own statements for that error; this preserves it one layer up.\r\n const parent =\r\n options?.injector?.get(EnvironmentInjector) ?? inject(EnvironmentInjector);\r\n\r\n // The other arm of the same problem, and the fallback above is what opens\r\n // it: *inside* an injection context that fallback succeeds, so a caller who\r\n // omitted the required `injector` would get a working binding parented at\r\n // the ambient environment injector - with no auto-teardown and no\r\n // diagnostic. That is the exact silent variation S 3.7 makes `injector`\r\n // required to prevent, so it is rejected here by name rather than left to\r\n // surface as a leak. Outside a context the line above has already thrown\r\n // NG0203 and this is unreachable.\r\n if (!options?.injector) {\r\n throw new Error(\r\n `bindFieldProperties requires an 'injector': every signal it creates is built ` +\r\n `with one and therefore takes no DestroyRef registration, so an ambient ` +\r\n `injection context would silently vary when teardown runs.`\r\n );\r\n }\r\n\r\n const scope = createEnvironmentInjector([], parent);\r\n\r\n const onError = options.onError ?? 'undefined';\r\n\r\n const fields: Record<string, FieldProperties> = {};\r\n\r\n // The signals, in creation order, so the `catch` below releases exactly what\r\n // exists rather than walking `fields` - which holds nothing for the field\r\n // that threw and nothing at all for a throw in `createControlSource`.\r\n const created: EvalSignal<unknown>[] = [];\r\n\r\n let destroyed = false;\r\n\r\n // The registration on the *caller's* injector, held so `destroy()` can drop\r\n // it. See below for why it exists at all.\r\n let unregister: (() => void) | undefined;\r\n\r\n const destroy = (): void => {\r\n\r\n if (destroyed) {\r\n return;\r\n }\r\n\r\n destroyed = true;\r\n\r\n // Cleared before it is called, so a teardown driven *by* the caller's\r\n // injector does not turn around and mutate the hook list that injector is\r\n // iterating - `eval-signal.ts:408-414`'s pattern and its reason.\r\n const release = unregister;\r\n unregister = undefined;\r\n release?.();\r\n\r\n try {\r\n // Called through the property rather than a captured reference: it is\r\n // the object the caller holds, and `coerce`'s `destroy` delegates to the\r\n // inner signal, so this is the one call that reaches both.\r\n for (const signal of created) {\r\n signal.destroy();\r\n }\r\n } finally {\r\n // In a `finally` because `destroyed` is already set: a signal whose\r\n // `destroy` throws - a consumer calling this from inside a `computed()`\r\n // reaches NG0600 - would otherwise leave the scope alive with the retry\r\n // guarded into a no-op, and every mirror subscription live with no way\r\n // back.\r\n //\r\n // Last, and idempotence is why the guard above exists at all:\r\n // `R3Injector.destroy()` opens with `assertNotDestroyed` and throws\r\n // NG0205 on a second call.\r\n scope.destroy();\r\n }\r\n };\r\n\r\n // **The scope is detached, not merely shorter-lived.**\r\n // `createEnvironmentInjector` does not register the child with its parent's\r\n // destroy hooks, so without this the mirror would outlive the injector the\r\n // caller scoped it to - and step 4, which passed that injector to\r\n // `createControlSource` directly, released everything when it died. A\r\n // consumer who wires a binding to a component or route injector and forgets\r\n // `destroy()` would otherwise retain N subscriptions, the mirror, the N\r\n // contexts and the form itself for the life of the root injector, silently.\r\n // `destroy()` stays the documented call; this is the net under it.\r\n unregister = options.injector.get(DestroyRef).onDestroy(() => {\r\n unregister = undefined;\r\n destroy();\r\n });\r\n\r\n try {\r\n\r\n // Scoped to the child, not to the caller's injector: it is what makes the\r\n // mirror's two subscriptions releasable at all without a handle.\r\n const formSource = createControlSource(group, { injector: scope });\r\n\r\n for (const field of schema) {\r\n\r\n const context = createFieldContext(formSource, {});\r\n const properties: { visible?: EvalSignal<boolean>; text?: EvalSignal<string> } = {};\r\n\r\n // `options.injector`, **not** the scope, and the distinction is not\r\n // cosmetic: `createEvalSignal` uses the injector it is given only to\r\n // resolve `CompilerService` (`eval-signal.ts:215-216`) - it registers no\r\n // teardown against it - and the scope's parent is\r\n // `caller.get(EnvironmentInjector)`, which for a node injector is an\r\n // *ancestor*. Passing the scope would therefore skip providers the\r\n // caller declared, silently, and diverge from step 4 for no gain. The\r\n // scope exists for the one thing that does need a releasable lifetime,\r\n // which is the mirror.\r\n if (field.visible !== undefined) {\r\n properties.visible = coerce(\r\n createEvalSignal(field.visible, context, {\r\n injector: options.injector,\r\n onError,\r\n }),\r\n toVisible\r\n );\r\n created.push(properties.visible);\r\n }\r\n\r\n if (field.text !== undefined) {\r\n properties.text = coerce(\r\n createEvalSignal(field.text, context, {\r\n injector: options.injector,\r\n onError,\r\n }),\r\n toText\r\n );\r\n created.push(properties.text);\r\n }\r\n\r\n fields[field.name] = properties;\r\n }\r\n\r\n } catch (error) {\r\n // A *parse* error is the reachable case (S 3.4.4's amendment):\r\n // `createEvalSignal` compiles eagerly, so `visible: 'country ==='` throws\r\n // from inside this loop with the mirror already open - and the caller\r\n // holds no handle to any of it, because the binding never returned.\r\n destroy();\r\n throw error;\r\n }\r\n\r\n return { fields, destroy };\r\n};\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}