@cratis/scene.react 4.7.0 → 4.8.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": "@cratis/scene.react",
3
- "version": "4.7.0",
3
+ "version": "4.8.0",
4
4
  "description": "The React renderer implementing Scene.Engine's Renderer contract against real React/DOM.",
5
5
  "keywords": [
6
6
  "cratis",
@@ -51,12 +51,12 @@
51
51
  "build-storybook": "storybook build"
52
52
  },
53
53
  "dependencies": {
54
- "@cratis/scene.engine": "4.7.0",
55
- "@cratis/scene.model": "4.7.0"
54
+ "@cratis/scene.engine": "4.8.0",
55
+ "@cratis/scene.model": "4.8.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@cratis/scene.engine": "4.7.0",
59
- "@cratis/scene.model": "4.7.0",
58
+ "@cratis/scene.engine": "4.8.0",
59
+ "@cratis/scene.model": "4.8.0",
60
60
  "@storybook/addon-links": "^10.6.1",
61
61
  "@storybook/react": "^10.6.1",
62
62
  "@storybook/react-vite": "^10.6.1",
@@ -0,0 +1,89 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ /**
5
+ * The web-only mapping between an independently authored Scene catalog and a Components renderer.
6
+ * Primitive slot identities are not Scene component names: composites such as DataTable remain
7
+ * library-independent. This contract belongs to the React bundle, never the platform-neutral model.
8
+ */
9
+ export interface ComponentsUiLibraryContract {
10
+ /** The npm module the host loads through its explicit package allowlist. */
11
+ module: string;
12
+ /** The named export supplying the Components UiLibrary. */
13
+ exportName: string;
14
+ /** The expected runtime library identity. */
15
+ id: string;
16
+ /** The Components renderer ABI major required by this mapping. */
17
+ abi: number;
18
+ /** The renderer profile promised by the library. */
19
+ profile: string;
20
+ /** Primitive or presentation slots the Scene integration requires. */
21
+ requiredSlots: readonly string[];
22
+ /** Capabilities required by the integration, not inferred from library identity. */
23
+ requiredCapabilities?: readonly string[];
24
+ }
25
+
26
+ /**
27
+ * The structural part of Components' UiLibrary needed to check a mapping. The actual library may
28
+ * also contain providers and executable slot declarations; Scene does not load or reinterpret them.
29
+ * Keeping this structural avoids a Components ABI dependency in Scene.Model or Scene.Engine.
30
+ */
31
+ export interface ComponentsUiLibraryManifest {
32
+ readonly id: string;
33
+ readonly abi: number;
34
+ readonly profile: string;
35
+ readonly slots: object;
36
+ readonly capabilities: readonly string[];
37
+ }
38
+
39
+ /** Checks a mapping before loading executable frontend code. */
40
+ export function validateComponentsUiLibraryContract(contract: ComponentsUiLibraryContract): string[] {
41
+ const problems: string[] = [];
42
+ for (const field of ['module', 'exportName', 'id', 'profile'] as const) {
43
+ if (contract[field].trim().length === 0) problems.push(`Components UI library mapping has an empty '${field}'`);
44
+ }
45
+ if (!Number.isInteger(contract.abi) || contract.abi < 1) {
46
+ problems.push('Components UI library mapping requires a positive integer ABI major');
47
+ }
48
+ for (const [field, values] of [
49
+ ['requiredSlots', contract.requiredSlots],
50
+ ['requiredCapabilities', contract.requiredCapabilities ?? []],
51
+ ] as const) {
52
+ const seen = new Set<string>();
53
+ for (const value of values) {
54
+ if (value.trim().length === 0) problems.push(`Components UI library mapping has an empty '${field}' entry`);
55
+ if (seen.has(value)) problems.push(`Components UI library mapping repeats '${value}' in '${field}'`);
56
+ seen.add(value);
57
+ }
58
+ }
59
+ return problems;
60
+ }
61
+
62
+ /**
63
+ * Proves that an allowlisted module export satisfies its Scene mapping. Hosts must reject problems,
64
+ * rather than silently falling back to a different library. Additional slots are permitted: a
65
+ * renderer can support more than the bounded profile this integration consumes.
66
+ */
67
+ export function validateComponentsUiLibraryMapping(
68
+ contract: ComponentsUiLibraryContract,
69
+ library: ComponentsUiLibraryManifest,
70
+ ): string[] {
71
+ const problems = validateComponentsUiLibraryContract(contract);
72
+ for (const field of ['id', 'abi', 'profile'] as const) {
73
+ if (library[field] !== contract[field]) {
74
+ problems.push(`Components UI library '${contract.id}' requires ${field} '${contract[field]}' but the loaded library supplies '${library[field]}'`);
75
+ }
76
+ }
77
+ const slots = new Map(Object.entries(library.slots));
78
+ for (const slot of contract.requiredSlots) {
79
+ if (!slots.has(slot) || slots.get(slot) === undefined || slots.get(slot) === null) {
80
+ problems.push(`Components UI library '${contract.id}' provides no implementation for required slot '${slot}'`);
81
+ }
82
+ }
83
+ for (const capability of contract.requiredCapabilities ?? []) {
84
+ if (!library.capabilities.includes(capability)) {
85
+ problems.push(`Components UI library '${contract.id}' does not support required capability '${capability}'`);
86
+ }
87
+ }
88
+ return problems;
89
+ }
@@ -5,6 +5,7 @@ import { ComponentDescriptor, DialogTemplate, Layout, PackageKind, PropertyValue
5
5
  import { IconAdapterRegistry, createIconAdapterRegistry } from '../icons/IconAdapterRegistry';
6
6
  import { IconLibraryBundle } from '../icons/IconLibraryBundle';
7
7
  import { ComponentRegistry } from '../renderer';
8
+ import { ComponentsUiLibraryContract, validateComponentsUiLibraryContract } from './ComponentsUiLibraryContract';
8
9
 
9
10
  /**
10
11
  * What a package actually ships to a React renderer, as opposed to what it *declares*.
@@ -70,6 +71,12 @@ export interface ScenePackageBundle {
70
71
  * manifest declares an icon library.
71
72
  */
72
73
  iconLibrary?: IconLibraryBundle;
74
+
75
+ /**
76
+ * Optional web-renderer mapping to a Components UiLibrary. Scene's catalog remains independently
77
+ * authored; hosts check the loaded export with validateComponentsUiLibraryMapping before rendering.
78
+ */
79
+ componentsUiLibrary?: ComponentsUiLibraryContract;
73
80
  }
74
81
 
75
82
  /**
@@ -140,6 +147,13 @@ export function validatePackageBundle(bundle: ScenePackageBundle): string[] {
140
147
  }
141
148
  }
142
149
 
150
+ if (bundle.componentsUiLibrary) {
151
+ if (manifest.kind !== PackageKind.ComponentLibrary) {
152
+ problems.push('declares a Components UI library mapping but is not of kind ComponentLibrary');
153
+ }
154
+ problems.push(...validateComponentsUiLibraryContract(bundle.componentsUiLibrary));
155
+ }
156
+
143
157
  problems.push(...validateIconLibrary(bundle));
144
158
  problems.push(...validateDescriptors(bundle));
145
159
 
package/packages/index.ts CHANGED
@@ -2,3 +2,4 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  export * from './ScenePackageBundle';
5
+ export * from './ComponentsUiLibraryContract';