ngx-json-render 0.3.0 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-json-render",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Angular renderer for json-render — stream AI-generated JSON specs into real Angular components with signals.",
5
5
  "keywords": [
6
6
  "angular",
@@ -1,5 +1,5 @@
1
1
  import * as _json_render_core from '@json-render/core';
2
- import { ResolvedAction, ActionHandler, ActionBinding, Spec, SpecIssue, Catalog, InferCatalogActions, InferActionParams, StateModel, InferCatalogComponents, UIElement, StateStore, ValidationFunction, ComputedFunction, DirectiveDefinition, ActionConfirm, DirectiveRegistry, ValidationResult, ValidationConfig, JsonPatch, FlatElement } from '@json-render/core';
2
+ import { ResolvedAction, ActionHandler, ActionBinding, SpecIssue, Spec, Catalog, InferCatalogActions, InferActionParams, StateModel, InferCatalogComponents, UIElement, StateStore, ValidationFunction, ComputedFunction, DirectiveDefinition, ActionConfirm, DirectiveRegistry, ValidationResult, ValidationConfig, JsonPatch, FlatElement } from '@json-render/core';
3
3
  export { ActionBinding, ActionHandler, JsonPatch, Spec, SpecIssue, StateModel, StateStore, UIElement, VisibilityCondition, autoFixSpec, createStateStore, formatSpecIssues, nestedToFlat, validateSpec } from '@json-render/core';
4
4
  import * as zod from 'zod';
5
5
  import * as zod_v4_core from 'zod/v4/core';
@@ -69,6 +69,93 @@ declare function injectAction(binding: ActionBinding): {
69
69
  isLoading: Signal<boolean>;
70
70
  };
71
71
 
72
+ /**
73
+ * Caps on what a spec is allowed to cost the browser.
74
+ *
75
+ * A spec is attacker-shaped input: whatever produced one chose its own size,
76
+ * its own nesting and — through `repeat` — its own array lengths. These are
77
+ * the three of those the renderer can enforce for you.
78
+ *
79
+ * Every field is opt-in and unset means unlimited. There are no defaults
80
+ * because no single number is right for every catalog, and a default would
81
+ * change what an existing app renders the moment it upgraded.
82
+ *
83
+ * Limits are enforced whenever they are set, whatever `validate` is: a number
84
+ * the app chose is a control, not advice. What "enforced" means differs by
85
+ * limit, because the limits differ in kind — see each field.
86
+ *
87
+ * @example
88
+ * ```html
89
+ * <json-render
90
+ * [spec]="spec()"
91
+ * [registry]="registry"
92
+ * [renderLimits]="{ maxElements: 500, maxDepth: 16, maxRepeatItems: 200 }"
93
+ * />
94
+ * ```
95
+ */
96
+ interface RenderLimits {
97
+ /**
98
+ * Most elements a spec may contain. A spec over the cap does not render at
99
+ * all: element count is a property of the whole spec, and there is no
100
+ * meaningful "first 500 elements" of a graph.
101
+ */
102
+ readonly maxElements?: number;
103
+ /**
104
+ * Deepest the render tree may nest, counting the root as 1. An element
105
+ * below the cap does not render, and neither does anything under it; what
106
+ * fits still renders. Reported as an error, so `validate="strict"` refuses
107
+ * the spec outright rather than truncating it.
108
+ *
109
+ * A capped element does not act either — its `watch` stays unwired, so it
110
+ * cannot dispatch actions from behind the cap.
111
+ */
112
+ readonly maxDepth?: number;
113
+ /**
114
+ * Most items one `repeat` may expand. The surplus items do not render.
115
+ *
116
+ * Enforced at render time only: `repeat` iterates a live state array, so no
117
+ * amount of inspecting the spec can predict how long it will be. Nested
118
+ * repeats multiply, so this is a per-repeat cap, not a total.
119
+ */
120
+ readonly maxRepeatItems?: number;
121
+ }
122
+ /**
123
+ * Issue codes this package reports, widening core's set with the ones only a
124
+ * renderer can find.
125
+ *
126
+ * - `cycle` — an element renders itself against the same data, which cannot
127
+ * terminate. A tree that recurses into its items is not this.
128
+ * - `too_deep` / `too_many_elements` — a {@link RenderLimits} cap was passed.
129
+ * - `unknown_component` — a `type` the catalog does not define.
130
+ * - `invalid_props` — the catalog's own schema rejected part of the spec.
131
+ */
132
+ type SpecCheckIssueCode = SpecIssue['code'] | 'cycle' | 'too_deep' | 'too_many_elements' | 'unknown_component' | 'invalid_props';
133
+ /**
134
+ * A problem found in a spec. Core's {@link SpecIssue} widened with the codes
135
+ * above, so a {@link SpecIssue} is always one of these.
136
+ */
137
+ interface SpecCheckIssue {
138
+ readonly severity: 'error' | 'warning';
139
+ readonly message: string;
140
+ readonly elementKey?: string;
141
+ readonly code: SpecCheckIssueCode;
142
+ }
143
+ /**
144
+ * The part of a json-render `Catalog` the check needs.
145
+ *
146
+ * Structural on purpose: a real catalog satisfies it, and the renderer stays
147
+ * free of any dependency on the schema library behind `validate`.
148
+ */
149
+ interface SpecCatalog {
150
+ /** Component type names the catalog defines. */
151
+ readonly componentNames: readonly string[];
152
+ /** Check a spec against the catalog's own schema. */
153
+ validate(spec: unknown): {
154
+ readonly success: boolean;
155
+ readonly error?: unknown;
156
+ };
157
+ }
158
+
72
159
  /**
73
160
  * How much a spec has to be trusted.
74
161
  *
@@ -77,8 +164,24 @@ declare function injectAction(binding: ActionBinding): {
77
164
  * - `warn` — check the spec, apply the lossless fixes, report what is left,
78
165
  * render it anyway.
79
166
  * - `strict` — the same check, but an error means the spec does not render.
167
+ *
168
+ * This governs *reporting* and strict refusal. {@link RenderLimits} are a
169
+ * separate axis: a limit the app set is enforced in every mode, including
170
+ * `off`.
80
171
  */
81
172
  type SpecValidationMode = 'off' | 'warn' | 'strict';
173
+ /** Everything {@link checkSpec} needs beyond the spec and the mode. */
174
+ interface SpecCheckOptions {
175
+ /** Caps on the spec's size, enforced whatever the mode. */
176
+ readonly limits?: RenderLimits | null;
177
+ /**
178
+ * The catalog the spec was generated for. Given one, the check also reports
179
+ * component types the catalog does not define and props its own schema
180
+ * rejects — the half of "is this spec renderable" that structure alone
181
+ * cannot answer.
182
+ */
183
+ readonly catalog?: SpecCatalog | null;
184
+ }
82
185
  /** What {@link checkSpec} found, and the spec that should render. */
83
186
  interface SpecCheck {
84
187
  /**
@@ -86,23 +189,41 @@ interface SpecCheck {
86
189
  * untouched when validation is off.
87
190
  */
88
191
  readonly spec: Spec | null;
89
- /** Structural issues left after the fixes. */
90
- readonly issues: readonly SpecIssue[];
192
+ /** Structural, limit and catalog issues left after the fixes. */
193
+ readonly issues: readonly SpecCheckIssue[];
91
194
  /** Descriptions of the fixes that were applied. */
92
195
  readonly fixes: readonly string[];
93
196
  /** Whether an issue of `error` severity survived. */
94
197
  readonly hasErrors: boolean;
198
+ /**
199
+ * Whether the spec is refused outright, in every mode, for breaching an
200
+ * enforced limit. Distinct from {@link hasErrors}, which only stops a spec
201
+ * under `strict`.
202
+ */
203
+ readonly blocked: boolean;
95
204
  }
96
205
  /**
97
- * Check a spec the way `validate` asks for.
206
+ * Check a spec the way `validate` and `renderLimits` ask for.
98
207
  *
99
208
  * Only lossless fixes are applied — `visible`, `on` and `repeat` that a model
100
209
  * put inside `props`, which the renderer would otherwise ignore, moved back
101
210
  * where they belong. The lossy ones prune content, and core's own guidance is
102
211
  * to re-prompt rather than accept that; a renderer that silently deleted
103
212
  * elements would be the worse failure.
213
+ *
214
+ * Note what is *not* here: cycles are broken as the tree renders, whatever
215
+ * this returns, because a spec that never stops rendering is a crash rather
216
+ * than an opinion about quality. This reports them so an app can see them.
104
217
  */
105
- declare function checkSpec(spec: Spec | null, mode: SpecValidationMode): SpecCheck;
218
+ declare function checkSpec(spec: Spec | null, mode: SpecValidationMode, options?: SpecCheckOptions): SpecCheck;
219
+ /**
220
+ * Render issues as one indented block, errors and warnings alike.
221
+ *
222
+ * Core's `formatSpecIssues` drops warnings and only accepts core's own issue
223
+ * codes, so it cannot describe what this package reports. Use this instead
224
+ * when showing `issues()` to a developer.
225
+ */
226
+ declare function formatSpecCheckIssues(issues: readonly SpecCheckIssue[]): string;
106
227
 
107
228
  /**
108
229
  * State setter function for updating application state.
@@ -341,6 +462,27 @@ declare class JsonRenderer {
341
462
  * arriving is expected to reference children that have not streamed in yet.
342
463
  */
343
464
  readonly validate: _angular_core.InputSignal<SpecValidationMode>;
465
+ /**
466
+ * Caps on what the spec may cost the browser: `maxElements`, `maxDepth` and
467
+ * `maxRepeatItems`. Unset by default, and each field is independently
468
+ * optional.
469
+ *
470
+ * Unlike `validate`, these are enforced in every mode — a number the app
471
+ * chose is a control, not a report. A spec over `maxElements` renders
472
+ * nothing; `maxDepth` and `maxRepeatItems` truncate where they are hit.
473
+ * They apply while `loading` too: a partial spec is a subset of the
474
+ * finished one, so a cap can only ever fire early, never falsely.
475
+ */
476
+ readonly renderLimits: _angular_core.InputSignal<RenderLimits | null>;
477
+ /**
478
+ * The catalog the spec was generated for. Given one, `validate` also checks
479
+ * what structure alone cannot: that every `type` is a component the catalog
480
+ * defines, and that the props match its schema.
481
+ *
482
+ * Only read when `validate` is on, and — like the rest of the check —
483
+ * skipped while `loading`.
484
+ */
485
+ readonly catalog: _angular_core.InputSignal<SpecCatalog | null>;
344
486
  /**
345
487
  * Initial state model (uncontrolled mode). Defaults to `spec.state`.
346
488
  * Ignored when `store` is provided.
@@ -390,7 +532,7 @@ declare class JsonRenderer {
390
532
  */
391
533
  protected readonly confirmInjector: _angular_core.Signal<_angular_core.DestroyableInjector | null>;
392
534
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<JsonRenderer, never>;
393
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<JsonRenderer, "json-render", never, { "spec": { "alias": "spec"; "required": true; "isSignal": true; }; "registry": { "alias": "registry"; "required": true; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "fallback": { "alias": "fallback"; "required": false; "isSignal": true; }; "validate": { "alias": "validate"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "store": { "alias": "store"; "required": false; "isSignal": true; }; "handlers": { "alias": "handlers"; "required": false; "isSignal": true; }; "onAction": { "alias": "onAction"; "required": false; "isSignal": true; }; "navigate": { "alias": "navigate"; "required": false; "isSignal": true; }; "validationFunctions": { "alias": "validationFunctions"; "required": false; "isSignal": true; }; "functions": { "alias": "functions"; "required": false; "isSignal": true; }; "directives": { "alias": "directives"; "required": false; "isSignal": true; }; }, { "stateChange": "stateChange"; }, never, never, true, never>;
535
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<JsonRenderer, "json-render", never, { "spec": { "alias": "spec"; "required": true; "isSignal": true; }; "registry": { "alias": "registry"; "required": true; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "fallback": { "alias": "fallback"; "required": false; "isSignal": true; }; "validate": { "alias": "validate"; "required": false; "isSignal": true; }; "renderLimits": { "alias": "renderLimits"; "required": false; "isSignal": true; }; "catalog": { "alias": "catalog"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "store": { "alias": "store"; "required": false; "isSignal": true; }; "handlers": { "alias": "handlers"; "required": false; "isSignal": true; }; "onAction": { "alias": "onAction"; "required": false; "isSignal": true; }; "navigate": { "alias": "navigate"; "required": false; "isSignal": true; }; "validationFunctions": { "alias": "validationFunctions"; "required": false; "isSignal": true; }; "functions": { "alias": "functions"; "required": false; "isSignal": true; }; "directives": { "alias": "directives"; "required": false; "isSignal": true; }; }, { "stateChange": "stateChange"; }, never, never, true, never>;
394
536
  }
395
537
 
396
538
  /**
@@ -424,6 +566,16 @@ declare class JrChildren {
424
566
  key?: string;
425
567
  } | undefined>;
426
568
  private readonly repeatBasePath;
569
+ /** Every item the repeat's state array holds, before any cap. */
570
+ private readonly allRepeatItems;
571
+ /**
572
+ * The items this repeat actually renders.
573
+ *
574
+ * `repeat` iterates a state array the spec may itself have supplied, which
575
+ * is the one place a spec sizes the render tree out of data rather than out
576
+ * of its own structure — so the cap has to be applied here, as it expands,
577
+ * rather than anywhere a spec can be inspected up front.
578
+ */
427
579
  protected readonly repeatItems: _angular_core.Signal<unknown[]>;
428
580
  constructor();
429
581
  protected itemPath(index: number): string;
@@ -445,6 +597,7 @@ declare class JrElement {
445
597
  private readonly state;
446
598
  private readonly actions;
447
599
  private readonly repeatScope;
600
+ private readonly parentPath;
448
601
  private readonly devtoolsActive;
449
602
  /** The raw (unresolved) element from the spec. */
450
603
  readonly rawElement: Signal<UIElement<string, Record<string, unknown>> | undefined>;
@@ -458,6 +611,23 @@ declare class JrElement {
458
611
  private readonly entry;
459
612
  protected readonly component: Signal<_angular_core.Type<unknown> | null>;
460
613
  protected readonly devtoolsKey: Signal<string | null>;
614
+ /**
615
+ * Where this element sits in the tree. Passed down to everything it renders
616
+ * below itself, which is what lets each element see its own ancestry
617
+ * without the renderer holding a registry of who is drawing what.
618
+ */
619
+ private readonly path;
620
+ /**
621
+ * Why this element is not drawn, or null when it is.
622
+ *
623
+ * The cycle half is unconditional — not something `validate` turns on. An
624
+ * element that renders itself without reading any deeper renders forever,
625
+ * and a spec that never stops rendering takes the tab with it; refusing to
626
+ * draw the repetition is the only outcome that leaves anything on screen.
627
+ * It costs one walk up a chain of parents, bounded by `maxDepth` where one
628
+ * is set and by the spec's own nesting where it is not.
629
+ */
630
+ protected readonly refusal: Signal<"cycle" | "depth" | null>;
461
631
  private readonly renderCtx;
462
632
  protected readonly outletInjector: _angular_core.DestroyableInjector;
463
633
  constructor();
@@ -646,6 +816,11 @@ declare class JsonRenderRootContext {
646
816
  registry: Signal<ComponentRegistry | undefined>;
647
817
  loading: Signal<boolean>;
648
818
  fallback: Signal<Type<unknown> | RegistryEntry | null | undefined>;
819
+ /**
820
+ * Caps on what the spec may cost the browser. Read as the tree renders, so
821
+ * they apply whatever `validate` is set to.
822
+ */
823
+ limits: Signal<RenderLimits | null | undefined>;
649
824
  /** External store (controlled mode). */
650
825
  store: Signal<StateStore | null | undefined>;
651
826
  /** Initial state (uncontrolled mode); falls back to `spec.state`. */
@@ -901,6 +1076,18 @@ interface UIStreamOptions {
901
1076
  * spec there does not persist a broken one.
902
1077
  */
903
1078
  validate?: SpecValidationMode;
1079
+ /**
1080
+ * Caps on what the finished spec may cost the browser. Enforced whatever
1081
+ * `validate` is: a spec over `maxElements` fails the generation rather than
1082
+ * reaching `onComplete`.
1083
+ */
1084
+ renderLimits?: RenderLimits | null;
1085
+ /**
1086
+ * The catalog the spec was generated for. Given one, `validate` also checks
1087
+ * every element `type` against the catalog and the props against its
1088
+ * schema — the half a structural check cannot see.
1089
+ */
1090
+ catalog?: SpecCatalog | null;
904
1091
  /**
905
1092
  * Transport, defaulting to the global `fetch`.
906
1093
  *
@@ -928,10 +1115,12 @@ interface UIStreamReturn {
928
1115
  /** Raw JSONL lines received from the stream (JSON patch lines) */
929
1116
  readonly rawLines: Signal<string[]>;
930
1117
  /**
931
- * Structural issues in the finished spec. Empty until a generation
932
- * completes, and always empty while `validate` is off.
1118
+ * What the check found in the finished spec — structure, catalog and
1119
+ * limits alike. Empty until a generation completes; empty while `validate`
1120
+ * is off unless a `renderLimits` cap was passed, since those are enforced
1121
+ * in every mode.
933
1122
  */
934
- readonly issues: Signal<readonly SpecIssue[]>;
1123
+ readonly issues: Signal<readonly SpecCheckIssue[]>;
935
1124
  /** Send a prompt to generate UI */
936
1125
  send: (prompt: string, options?: UIStreamSendOptions) => Promise<void>;
937
1126
  /**
@@ -998,6 +1187,18 @@ interface ChatUIOptions {
998
1187
  * reply that produced a spec.
999
1188
  */
1000
1189
  validate?: SpecValidationMode;
1190
+ /**
1191
+ * Caps on what the finished spec may cost the browser. Enforced whatever
1192
+ * `validate` is: a spec over `maxElements` fails the generation rather than
1193
+ * reaching `onComplete`.
1194
+ */
1195
+ renderLimits?: RenderLimits | null;
1196
+ /**
1197
+ * The catalog the spec was generated for. Given one, `validate` also checks
1198
+ * every element `type` against the catalog and the props against its
1199
+ * schema — the half a structural check cannot see.
1200
+ */
1201
+ catalog?: SpecCatalog | null;
1001
1202
  }
1002
1203
  /**
1003
1204
  * Return type for {@link injectChatUI}.
@@ -1010,10 +1211,12 @@ interface ChatUIReturn {
1010
1211
  /** Error from the last request, if any */
1011
1212
  readonly error: Signal<Error | null>;
1012
1213
  /**
1013
- * Structural issues in the spec of the last completed reply. Empty until a
1014
- * reply completes, and always empty while `validate` is off.
1214
+ * What the check found in the last completed reply's spec — structure,
1215
+ * catalog and limits alike. Empty until a reply completes; empty while
1216
+ * `validate` is off unless a `renderLimits` cap was passed, since those are
1217
+ * enforced in every mode.
1015
1218
  */
1016
- readonly issues: Signal<readonly SpecIssue[]>;
1219
+ readonly issues: Signal<readonly SpecCheckIssue[]>;
1017
1220
  /** Send a user message */
1018
1221
  send: (text: string) => Promise<void>;
1019
1222
  /**
@@ -1110,5 +1313,5 @@ type AngularSpec<TCatalog> = typeof schema extends {
1110
1313
  */
1111
1314
  declare function injectDevtoolsActive(): Signal<boolean>;
1112
1315
 
1113
- export { CONFIRM_CONTEXT, ELEMENT_KEY, JR_CONFIRM_DIALOG, JR_CONFIRM_LABELS, JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, checkSpec, createStoreSetState, defineRegistry, flatToTree, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage, schema };
1114
- export type { ActionFn, Actions, AngularSchema, AngularSpec, CatalogHasActions, ChatMessage, ChatUIOptions, ChatUIReturn, ComponentRegistry, Components, ConfirmContext, ConfirmLabels, DataPart, DefineRegistryResult, EventHandle, FieldValidationState, PendingConfirmation, RegistryEntry, RenderContext, RepeatScope, SetState, SpecCheck, SpecValidationMode, StateChange, TokenUsage, UIStreamOptions, UIStreamReturn, UIStreamSendOptions };
1316
+ export { CONFIRM_CONTEXT, ELEMENT_KEY, JR_CONFIRM_DIALOG, JR_CONFIRM_LABELS, JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, checkSpec, createStoreSetState, defineRegistry, flatToTree, formatSpecCheckIssues, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage, schema };
1317
+ export type { ActionFn, Actions, AngularSchema, AngularSpec, CatalogHasActions, ChatMessage, ChatUIOptions, ChatUIReturn, ComponentRegistry, Components, ConfirmContext, ConfirmLabels, DataPart, DefineRegistryResult, EventHandle, FieldValidationState, PendingConfirmation, RegistryEntry, RenderContext, RenderLimits, RepeatScope, SetState, SpecCatalog, SpecCheck, SpecCheckIssue, SpecCheckIssueCode, SpecCheckOptions, SpecValidationMode, StateChange, TokenUsage, UIStreamOptions, UIStreamReturn, UIStreamSendOptions };