@usefragments/core 1.10.2 → 2.0.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.
Files changed (130) hide show
  1. package/dist/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
  2. package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
  3. package/dist/chunk-7ULGH74M.js +66 -0
  4. package/dist/chunk-7ULGH74M.js.map +1 -0
  5. package/dist/{chunk-XN3LSDPY.js → chunk-BMPYIUZE.js} +612 -344
  6. package/dist/chunk-BMPYIUZE.js.map +1 -0
  7. package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
  8. package/dist/chunk-ML5S6QNU.js.map +1 -0
  9. package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
  10. package/dist/chunk-MZ2FS7U4.js.map +1 -0
  11. package/dist/chunk-PWIJMOI4.js +202 -0
  12. package/dist/chunk-PWIJMOI4.js.map +1 -0
  13. package/dist/chunk-RYFULE43.js +578 -0
  14. package/dist/chunk-RYFULE43.js.map +1 -0
  15. package/dist/codes/index.d.ts +2 -2
  16. package/dist/codes/index.js +3 -2
  17. package/dist/compiled-types/index.d.ts +327 -2
  18. package/dist/compiled-types/index.js +1 -1
  19. package/dist/generate/index.d.ts +3 -2
  20. package/dist/{governance-CLk_wkP9.d.ts → governance-hOPXGbbs.d.ts} +474 -515
  21. package/dist/governance-telemetry.d.ts +6 -0
  22. package/dist/governance-telemetry.js +1 -1
  23. package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
  24. package/dist/index.d.ts +3280 -1305
  25. package/dist/index.js +2414 -350
  26. package/dist/index.js.map +1 -1
  27. package/dist/manifest.d.ts +228 -0
  28. package/dist/manifest.js +24 -0
  29. package/dist/manifest.js.map +1 -0
  30. package/dist/preview/index.js +45 -1
  31. package/dist/preview/index.js.map +1 -1
  32. package/dist/preview-runtime.d.ts +1 -2
  33. package/dist/preview-runtime.js +150 -14
  34. package/dist/preview-runtime.js.map +1 -1
  35. package/dist/react-types.d.ts +1 -2
  36. package/dist/registry.d.ts +1412 -203
  37. package/dist/registry.js +30 -3
  38. package/dist/schemas/index.d.ts +1 -1
  39. package/dist/schemas/index.js +3 -2
  40. package/dist/storyAdapter.d.ts +1 -2
  41. package/dist/storyAdapter.js +11 -49
  42. package/dist/storyAdapter.js.map +1 -1
  43. package/dist/test-utils.d.ts +3 -2
  44. package/dist/topology/index.d.ts +1 -1
  45. package/dist/topology/index.js +1 -1
  46. package/package.json +8 -2
  47. package/src/__tests__/contract-parser.test.ts +318 -277
  48. package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
  49. package/src/__tests__/preview-runtime.test.tsx +30 -8
  50. package/src/__tests__/schema.test.ts +191 -14
  51. package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
  52. package/src/analysis-plan/coverage.ts +181 -0
  53. package/src/analysis-plan/digest.ts +141 -0
  54. package/src/analysis-plan/index.ts +34 -0
  55. package/src/analysis-plan/types.ts +207 -0
  56. package/src/approved-contract-tokens.test.ts +39 -0
  57. package/src/approved-contract-tokens.ts +18 -0
  58. package/src/codes/__tests__/codes.test.ts +13 -0
  59. package/src/codes/codes.ts +40 -0
  60. package/src/compiled-types/index.ts +640 -39
  61. package/src/compiled-types/parse.test.ts +145 -4
  62. package/src/component-contract.ts +95 -53
  63. package/src/composition.ts +7 -13
  64. package/src/constants.ts +3 -6
  65. package/src/contract/hash.test.ts +20 -0
  66. package/src/contract/hash.ts +66 -9
  67. package/src/contract/index.ts +24 -1
  68. package/src/contract/manifest.test.ts +94 -0
  69. package/src/contract/manifest.ts +68 -0
  70. package/src/contract/preimage.test.ts +219 -1
  71. package/src/contract/preimage.ts +326 -6
  72. package/src/contract/stamp.test.ts +3 -0
  73. package/src/contract/stamp.ts +1 -1
  74. package/src/contract-parser.ts +54 -30
  75. package/src/defineFragment.test.ts +476 -91
  76. package/src/defineFragment.ts +204 -114
  77. package/src/domain-ids.test.ts +35 -0
  78. package/src/domain-ids.ts +61 -0
  79. package/src/evaluation/evaluate.test.ts +522 -0
  80. package/src/evaluation/evaluate.ts +690 -0
  81. package/src/evaluation/evaluation-v2-receipt-v1.test.ts +772 -0
  82. package/src/evaluation/index.ts +58 -0
  83. package/src/evaluation/receipt.ts +753 -0
  84. package/src/evaluation/types.ts +406 -0
  85. package/src/facts/builders.ts +2 -0
  86. package/src/facts/compile.ts +29 -6
  87. package/src/facts/fact-index.ts +13 -3
  88. package/src/facts/fact-integrity-v1.test.ts +172 -0
  89. package/src/facts/facts.test.ts +15 -0
  90. package/src/facts/ids.ts +46 -3
  91. package/src/facts/index.ts +14 -1
  92. package/src/facts/integrity.ts +134 -0
  93. package/src/facts/types.ts +36 -0
  94. package/src/governance-integrity.test.ts +1 -0
  95. package/src/governance-integrity.ts +5 -3
  96. package/src/governance-telemetry.ts +8 -0
  97. package/src/governance.ts +70 -8
  98. package/src/index.ts +230 -37
  99. package/src/preview/validation.test.ts +62 -0
  100. package/src/preview/validation.ts +48 -2
  101. package/src/preview-runtime.tsx +227 -20
  102. package/src/registry-install-plan.ts +200 -109
  103. package/src/registry-shards.test.ts +263 -0
  104. package/src/registry.ts +237 -0
  105. package/src/repository-binding.test.ts +50 -0
  106. package/src/repository-binding.ts +96 -0
  107. package/src/rules/families.test.ts +36 -0
  108. package/src/rules/finding.ts +7 -2
  109. package/src/rules/index.ts +17 -1
  110. package/src/rules/rule-config.test.ts +66 -0
  111. package/src/rules/rule-config.ts +73 -0
  112. package/src/rules/rules.test.ts +26 -0
  113. package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
  114. package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
  115. package/src/schema.ts +293 -113
  116. package/src/schemas/index.ts +1 -1
  117. package/src/storyAdapter.test.ts +68 -12
  118. package/src/storyAdapter.ts +44 -75
  119. package/src/topology/resolve-area.ts +1 -1
  120. package/src/types.ts +258 -40
  121. package/dist/chunk-3LLRNCPX.js.map +0 -1
  122. package/dist/chunk-RANPUC6C.js +0 -72
  123. package/dist/chunk-RANPUC6C.js.map +0 -1
  124. package/dist/chunk-XN3LSDPY.js.map +0 -1
  125. package/dist/chunk-YF65VYRY.js.map +0 -1
  126. package/src/fragment-types.ts +0 -214
  127. package/src/react-create-element.test.ts +0 -22
  128. package/src/react-create-element.ts +0 -12
  129. package/src/storyFilters.test.ts +0 -350
  130. package/src/storyFilters.ts +0 -253
@@ -1,4 +1,4 @@
1
- import { useEffect, useState, type ReactNode } from "react";
1
+ import { useEffect, useRef, useState, type ReactNode } from "react";
2
2
  import type { VariantLoader, VariantRenderOptions } from "./types.js";
3
3
 
4
4
  /**
@@ -29,17 +29,208 @@ const EMPTY_STATE: PreviewRuntimeState = {
29
29
  loadedData: undefined,
30
30
  };
31
31
 
32
+ const LOADING_STATE: PreviewRuntimeState = {
33
+ content: null,
34
+ isLoading: true,
35
+ error: null,
36
+ loadedData: undefined,
37
+ };
38
+
39
+ const LOADED_DATA_COMPARISON_MAX_DEPTH = 32;
40
+ const LOADED_DATA_COMPARISON_MAX_OBJECTS = 1_000;
41
+ const LOADED_DATA_COMPARISON_MAX_PROPERTIES = 4_000;
42
+
43
+ const LOADED_DATA_COMPARISON_ERROR = new Error(
44
+ "Preview loadedData could not be compared safely. Pass a stable object reference, or use " +
45
+ `JSON-like data within ${LOADED_DATA_COMPARISON_MAX_DEPTH} levels, ` +
46
+ `${LOADED_DATA_COMPARISON_MAX_OBJECTS} objects, and ` +
47
+ `${LOADED_DATA_COMPARISON_MAX_PROPERTIES} property inspections.`
48
+ );
49
+
50
+ const LOADED_DATA_COMPARISON_ERROR_STATE: PreviewRuntimeState = {
51
+ content: null,
52
+ isLoading: false,
53
+ error: LOADED_DATA_COMPARISON_ERROR,
54
+ loadedData: undefined,
55
+ };
56
+
57
+ interface PreviewRuntimeResolution {
58
+ variant: PreviewVariantLike | null;
59
+ loadedData?: Record<string, unknown>;
60
+ state: PreviewRuntimeState;
61
+ }
62
+
32
63
  function toError(error: unknown): Error {
33
64
  return error instanceof Error ? error : new Error(String(error));
34
65
  }
35
66
 
67
+ type LoadedDataComparison = "equal" | "different" | "indeterminate";
68
+ const NO_PENDING_LOADED_DATA = Symbol("no-pending-loaded-data");
69
+
70
+ interface LoadedDataComparisonContext {
71
+ comparedObjects: number;
72
+ inspectedProperties: number;
73
+ leftToRight: WeakMap<object, object>;
74
+ rightToLeft: WeakMap<object, object>;
75
+ }
76
+
77
+ function isPlainRecord(value: object): value is Record<string, unknown> {
78
+ const prototype = Object.getPrototypeOf(value);
79
+ return prototype === Object.prototype || prototype === null;
80
+ }
81
+
82
+ function compareLoadedDataProperties(
83
+ left: object,
84
+ right: object,
85
+ depth: number,
86
+ context: LoadedDataComparisonContext
87
+ ): LoadedDataComparison {
88
+ let leftPropertyCount = 0;
89
+ for (const key in left) {
90
+ if (context.inspectedProperties >= LOADED_DATA_COMPARISON_MAX_PROPERTIES) {
91
+ return "indeterminate";
92
+ }
93
+ context.inspectedProperties += 1;
94
+
95
+ if (!Object.prototype.hasOwnProperty.call(left, key)) continue;
96
+ leftPropertyCount += 1;
97
+ if (!Object.prototype.hasOwnProperty.call(right, key)) return "different";
98
+
99
+ const leftDescriptor = Object.getOwnPropertyDescriptor(left, key);
100
+ const rightDescriptor = Object.getOwnPropertyDescriptor(right, key);
101
+ if (
102
+ !leftDescriptor ||
103
+ !rightDescriptor ||
104
+ !("value" in leftDescriptor) ||
105
+ !("value" in rightDescriptor)
106
+ ) {
107
+ return "indeterminate";
108
+ }
109
+
110
+ const comparison = compareLoadedDataValue(
111
+ leftDescriptor.value,
112
+ rightDescriptor.value,
113
+ depth + 1,
114
+ context
115
+ );
116
+ if (comparison !== "equal") return comparison;
117
+ }
118
+
119
+ let rightPropertyCount = 0;
120
+ for (const key in right) {
121
+ if (context.inspectedProperties >= LOADED_DATA_COMPARISON_MAX_PROPERTIES) {
122
+ return "indeterminate";
123
+ }
124
+ context.inspectedProperties += 1;
125
+ if (Object.prototype.hasOwnProperty.call(right, key)) rightPropertyCount += 1;
126
+ }
127
+
128
+ return leftPropertyCount === rightPropertyCount ? "equal" : "different";
129
+ }
130
+
131
+ function compareLoadedDataValue(
132
+ left: unknown,
133
+ right: unknown,
134
+ depth: number,
135
+ context: LoadedDataComparisonContext
136
+ ): LoadedDataComparison {
137
+ if (Object.is(left, right)) return "equal";
138
+
139
+ const leftType = typeof left;
140
+ const rightType = typeof right;
141
+ if (leftType !== rightType || left === null || right === null) return "different";
142
+ if (leftType === "function" || leftType === "symbol") return "indeterminate";
143
+ if (leftType !== "object") return "different";
144
+
145
+ const leftObject = left as object;
146
+ const rightObject = right as object;
147
+ const pairedRight = context.leftToRight.get(leftObject);
148
+ const pairedLeft = context.rightToLeft.get(rightObject);
149
+ if (pairedRight || pairedLeft) {
150
+ return pairedRight === rightObject && pairedLeft === leftObject ? "equal" : "different";
151
+ }
152
+
153
+ if (depth >= LOADED_DATA_COMPARISON_MAX_DEPTH) return "indeterminate";
154
+ if (context.comparedObjects >= LOADED_DATA_COMPARISON_MAX_OBJECTS) {
155
+ return "indeterminate";
156
+ }
157
+
158
+ const leftIsArray = Array.isArray(leftObject);
159
+ const rightIsArray = Array.isArray(rightObject);
160
+ if (leftIsArray !== rightIsArray) return "different";
161
+ if (leftIsArray && (leftObject as unknown[]).length !== (rightObject as unknown[]).length) {
162
+ return "different";
163
+ }
164
+ if (!leftIsArray) {
165
+ const leftIsPlainRecord = isPlainRecord(leftObject);
166
+ const rightIsPlainRecord = isPlainRecord(rightObject);
167
+ if (leftIsPlainRecord !== rightIsPlainRecord) return "different";
168
+ if (!leftIsPlainRecord) return "indeterminate";
169
+ }
170
+
171
+ context.comparedObjects += 1;
172
+ context.leftToRight.set(leftObject, rightObject);
173
+ context.rightToLeft.set(rightObject, leftObject);
174
+
175
+ return compareLoadedDataProperties(leftObject, rightObject, depth, context);
176
+ }
177
+
178
+ function compareLoadedData(
179
+ left: Record<string, unknown> | undefined,
180
+ right: Record<string, unknown> | undefined
181
+ ): LoadedDataComparison {
182
+ try {
183
+ return compareLoadedDataValue(left, right, 0, {
184
+ comparedObjects: 0,
185
+ inspectedProperties: 0,
186
+ leftToRight: new WeakMap(),
187
+ rightToLeft: new WeakMap(),
188
+ });
189
+ } catch {
190
+ // Host data must not make render fail when an accessor or proxy cannot be inspected safely.
191
+ return "indeterminate";
192
+ }
193
+ }
194
+
195
+ interface StableLoadedDataResult {
196
+ loadedData: Record<string, unknown> | undefined;
197
+ comparisonError: Error | null;
198
+ }
199
+
200
+ function useStableLoadedData(
201
+ loadedData: Record<string, unknown> | undefined
202
+ ): StableLoadedDataResult {
203
+ const stableLoadedData = useRef(loadedData);
204
+ const pendingLoadedData = useRef<
205
+ Record<string, unknown> | undefined | typeof NO_PENDING_LOADED_DATA
206
+ >(NO_PENDING_LOADED_DATA);
207
+ const comparison = compareLoadedData(stableLoadedData.current, loadedData);
208
+
209
+ if (comparison === "equal") {
210
+ pendingLoadedData.current = NO_PENDING_LOADED_DATA;
211
+ return { loadedData: stableLoadedData.current, comparisonError: null };
212
+ }
213
+
214
+ if (comparison === "different" || pendingLoadedData.current === loadedData) {
215
+ stableLoadedData.current = loadedData;
216
+ pendingLoadedData.current = NO_PENDING_LOADED_DATA;
217
+ return { loadedData, comparisonError: null };
218
+ }
219
+
220
+ pendingLoadedData.current = loadedData;
221
+ return {
222
+ loadedData: stableLoadedData.current,
223
+ comparisonError: LOADED_DATA_COMPARISON_ERROR,
224
+ };
225
+ }
226
+
36
227
  /**
37
228
  * Execute all variant loaders and merge their payloads.
38
229
  * `loadedData` is applied last so host-level overrides win.
39
230
  */
40
231
  export async function executeVariantLoaders(
41
232
  loaders: VariantLoader[] | undefined,
42
- loadedData?: Record<string, unknown>,
233
+ loadedData?: Record<string, unknown>
43
234
  ): Promise<Record<string, unknown> | undefined> {
44
235
  const hasLoaders = !!loaders && loaders.length > 0;
45
236
  if (!hasLoaders) {
@@ -49,7 +240,7 @@ export async function executeVariantLoaders(
49
240
  const results = await Promise.all(loaders.map((loader) => loader()));
50
241
  const mergedFromLoaders = results.reduce<Record<string, unknown>>(
51
242
  (acc, result) => ({ ...acc, ...result }),
52
- {},
243
+ {}
53
244
  );
54
245
 
55
246
  return loadedData ? { ...mergedFromLoaders, ...loadedData } : mergedFromLoaders;
@@ -60,7 +251,7 @@ export async function executeVariantLoaders(
60
251
  * This is useful for testing and for hook/component orchestration.
61
252
  */
62
253
  export async function resolvePreviewRuntimeState(
63
- options: PreviewRuntimeOptions,
254
+ options: PreviewRuntimeOptions
64
255
  ): Promise<PreviewRuntimeState> {
65
256
  const { variant, loadedData } = options;
66
257
  if (!variant) {
@@ -89,42 +280,58 @@ export async function resolvePreviewRuntimeState(
89
280
  /**
90
281
  * Hook for rendering a preview variant with loader support.
91
282
  */
92
- export function usePreviewVariantRuntime(
93
- options: PreviewRuntimeOptions,
94
- ): PreviewRuntimeState {
95
- const { variant, loadedData } = options;
96
- const [state, setState] = useState<PreviewRuntimeState>(EMPTY_STATE);
283
+ export function usePreviewVariantRuntime(options: PreviewRuntimeOptions): PreviewRuntimeState {
284
+ const { variant } = options;
285
+ const { loadedData, comparisonError } = useStableLoadedData(options.loadedData);
286
+ const [resolution, setResolution] = useState<PreviewRuntimeResolution>({
287
+ variant: null,
288
+ loadedData: undefined,
289
+ state: EMPTY_STATE,
290
+ });
97
291
 
98
292
  useEffect(() => {
99
293
  let cancelled = false;
100
294
 
101
295
  if (!variant) {
102
- setState(EMPTY_STATE);
296
+ setResolution({ variant: null, loadedData, state: EMPTY_STATE });
103
297
  return () => {
104
298
  cancelled = true;
105
299
  };
106
300
  }
107
301
 
108
- const hasLoaders = !!variant.loaders && variant.loaders.length > 0;
109
- setState({
110
- content: null,
111
- isLoading: hasLoaders,
112
- error: null,
113
- loadedData: undefined,
114
- });
302
+ if (comparisonError) {
303
+ setResolution({ variant, loadedData, state: LOADED_DATA_COMPARISON_ERROR_STATE });
304
+ return () => {
305
+ cancelled = true;
306
+ };
307
+ }
308
+
309
+ setResolution({ variant, loadedData, state: LOADING_STATE });
115
310
 
116
311
  resolvePreviewRuntimeState({ variant, loadedData }).then((nextState) => {
117
312
  if (!cancelled) {
118
- setState(nextState);
313
+ setResolution({ variant, loadedData, state: nextState });
119
314
  }
120
315
  });
121
316
 
122
317
  return () => {
123
318
  cancelled = true;
124
319
  };
125
- }, [variant, loadedData]);
320
+ }, [variant, loadedData, comparisonError]);
321
+
322
+ if (!variant) {
323
+ return EMPTY_STATE;
324
+ }
325
+
326
+ if (comparisonError) {
327
+ return LOADED_DATA_COMPARISON_ERROR_STATE;
328
+ }
329
+
330
+ if (resolution.variant !== variant || resolution.loadedData !== loadedData) {
331
+ return LOADING_STATE;
332
+ }
126
333
 
127
- return state;
334
+ return resolution.state;
128
335
  }
129
336
 
130
337
  interface PreviewVariantRuntimeProps extends PreviewRuntimeOptions {