@ankhorage/zora 4.2.0 → 4.3.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.
@@ -0,0 +1,278 @@
1
+ import type { UiComponentMetaRegistry, UiComponentPackageManifest } from '@ankhorage/contracts';
2
+
3
+ import type { ZoraComponentMeta, ZoraComponentMetaRegistry } from './metadata/types';
4
+ import type { ZoraComponentRegistry } from './registry';
5
+
6
+ export type ZoraPluginCompositionErrorCode =
7
+ | 'duplicate-component'
8
+ | 'duplicate-package'
9
+ | 'invalid-component-meta'
10
+ | 'invalid-interaction-policy-component'
11
+ | 'invalid-placement-child'
12
+ | 'invalid-placement-parent'
13
+ | 'missing-component-meta'
14
+ | 'missing-runtime-component';
15
+
16
+ export interface ZoraPluginPlacement {
17
+ readonly child: string;
18
+ readonly parents: readonly string[];
19
+ }
20
+
21
+ export interface ZoraPluginMetadata {
22
+ readonly packageName: string;
23
+ readonly displayName?: string;
24
+ readonly componentMeta: ZoraComponentMetaRegistry;
25
+ readonly extensionHosts?: readonly string[];
26
+ readonly placements?: readonly ZoraPluginPlacement[];
27
+ readonly interactionPolicySupportedComponents?: readonly string[];
28
+ }
29
+
30
+ export interface ZoraPluginDescriptor extends ZoraPluginMetadata {
31
+ readonly componentRegistry: ZoraComponentRegistry;
32
+ }
33
+
34
+ export interface ComposedZoraPluginMetadataCatalog {
35
+ readonly componentMeta: ZoraComponentMetaRegistry;
36
+ readonly bindableComponentMeta: UiComponentMetaRegistry;
37
+ readonly packageManifests: readonly UiComponentPackageManifest[];
38
+ readonly interactionPolicySupportedComponents: Readonly<Record<string, true>>;
39
+ }
40
+
41
+ export interface ComposedZoraPluginCatalog extends ComposedZoraPluginMetadataCatalog {
42
+ readonly componentRegistry: ZoraComponentRegistry;
43
+ }
44
+
45
+ /*** Report one deterministic plugin composition contract violation. */
46
+ export class ZoraPluginCompositionError extends Error {
47
+ readonly code: ZoraPluginCompositionErrorCode;
48
+ readonly packageName: string;
49
+ readonly componentName?: string;
50
+
51
+ constructor(args: {
52
+ readonly code: ZoraPluginCompositionErrorCode;
53
+ readonly packageName: string;
54
+ readonly message: string;
55
+ readonly componentName?: string;
56
+ }) {
57
+ super(args.message);
58
+ this.name = 'ZoraPluginCompositionError';
59
+ this.code = args.code;
60
+ this.packageName = args.packageName;
61
+ this.componentName = args.componentName;
62
+ }
63
+ }
64
+
65
+ /*** Compose a selected ZORA metadata set for authoring tools that must not load React Native runtime modules. */
66
+ export function composeZoraPluginMetadata(
67
+ plugins: readonly ZoraPluginMetadata[],
68
+ ): ComposedZoraPluginMetadataCatalog {
69
+ const descriptors = sortPlugins(plugins);
70
+ const packages = new Set<string>();
71
+ const componentOwners = new Map<string, string>();
72
+ const componentMeta = new Map<string, ZoraComponentMeta>();
73
+ const extensionHosts = new Set<string>();
74
+ const interactionPolicySupportedComponents = new Set<string>();
75
+
76
+ for (const descriptor of descriptors) {
77
+ validateUniquePackage(descriptor, packages);
78
+ validateMetadata(descriptor);
79
+ for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {
80
+ claimComponent(componentName, descriptor.packageName, componentOwners);
81
+ componentMeta.set(componentName, meta);
82
+ }
83
+ for (const componentName of descriptor.extensionHosts ?? []) extensionHosts.add(componentName);
84
+ for (const componentName of descriptor.interactionPolicySupportedComponents ?? []) {
85
+ interactionPolicySupportedComponents.add(componentName);
86
+ }
87
+ }
88
+
89
+ for (const descriptor of descriptors) {
90
+ for (const placement of descriptor.placements ?? []) {
91
+ applyPlacement({ descriptor, placement, componentMeta, extensionHosts });
92
+ }
93
+ }
94
+
95
+ return {
96
+ componentMeta: Object.fromEntries(componentMeta),
97
+ bindableComponentMeta: Object.fromEntries(componentMeta),
98
+ packageManifests: descriptors.map(({ packageName, displayName, componentMeta }) => ({
99
+ packageName,
100
+ ...(displayName === undefined ? {} : { displayName }),
101
+ components: componentMeta,
102
+ })),
103
+ interactionPolicySupportedComponents: Object.fromEntries(
104
+ [...interactionPolicySupportedComponents]
105
+ .sort()
106
+ .map((componentName) => [componentName, true]),
107
+ ),
108
+ };
109
+ }
110
+
111
+ /*** Compose a selected ZORA core/plugin descriptor set into one validated authoring/runtime catalog. */
112
+ export function composeZoraPlugins(
113
+ plugins: readonly ZoraPluginDescriptor[],
114
+ ): ComposedZoraPluginCatalog {
115
+ const descriptors = sortPlugins(plugins);
116
+ const componentRegistry = new Map<string, ZoraComponentRegistry[string]>();
117
+
118
+ for (const descriptor of descriptors) {
119
+ validateRuntime(descriptor);
120
+ for (const [componentName, component] of sortedEntries(descriptor.componentRegistry)) {
121
+ componentRegistry.set(componentName, component);
122
+ }
123
+ }
124
+ const metadataCatalog = composeZoraPluginMetadata(descriptors);
125
+
126
+ return { ...metadataCatalog, componentRegistry: Object.fromEntries(componentRegistry) };
127
+ }
128
+
129
+ function validateUniquePackage(descriptor: ZoraPluginMetadata, packages: Set<string>): void {
130
+ if (packages.has(descriptor.packageName)) {
131
+ throw compositionError(
132
+ 'duplicate-package',
133
+ descriptor.packageName,
134
+ `Duplicate ZORA plugin package '${descriptor.packageName}'.`,
135
+ );
136
+ }
137
+ packages.add(descriptor.packageName);
138
+ }
139
+
140
+ function validateMetadata(descriptor: ZoraPluginMetadata): void {
141
+ for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {
142
+ if (meta.name !== componentName) {
143
+ throw compositionError(
144
+ 'invalid-component-meta',
145
+ descriptor.packageName,
146
+ `ZORA plugin '${descriptor.packageName}' metadata key '${componentName}' does not match meta.name '${meta.name}'.`,
147
+ componentName,
148
+ );
149
+ }
150
+ }
151
+ }
152
+
153
+ function validateRuntime(descriptor: ZoraPluginDescriptor): void {
154
+ const componentMetaNames = new Set(Object.keys(descriptor.componentMeta));
155
+ const runtimeComponentNames = new Set(Object.keys(descriptor.componentRegistry));
156
+ for (const componentName of sortedKeys(descriptor.componentRegistry)) {
157
+ if (!componentMetaNames.has(componentName)) {
158
+ throw compositionError(
159
+ 'missing-component-meta',
160
+ descriptor.packageName,
161
+ `ZORA plugin '${descriptor.packageName}' registers runtime component '${componentName}' without component metadata.`,
162
+ componentName,
163
+ );
164
+ }
165
+ }
166
+ for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {
167
+ if (meta.directManifestNode && !runtimeComponentNames.has(componentName)) {
168
+ throw compositionError(
169
+ 'missing-runtime-component',
170
+ descriptor.packageName,
171
+ `ZORA plugin '${descriptor.packageName}' exposes direct manifest node '${componentName}' without a runtime component.`,
172
+ componentName,
173
+ );
174
+ }
175
+ }
176
+ for (const componentName of descriptor.interactionPolicySupportedComponents ?? []) {
177
+ if (!runtimeComponentNames.has(componentName)) {
178
+ throw compositionError(
179
+ 'invalid-interaction-policy-component',
180
+ descriptor.packageName,
181
+ `ZORA plugin '${descriptor.packageName}' declares interaction-policy support for unregistered component '${componentName}'.`,
182
+ componentName,
183
+ );
184
+ }
185
+ }
186
+ }
187
+
188
+ function claimComponent(
189
+ componentName: string,
190
+ packageName: string,
191
+ owners: Map<string, string>,
192
+ ): void {
193
+ const existingOwner = owners.get(componentName);
194
+ if (existingOwner) {
195
+ throw compositionError(
196
+ 'duplicate-component',
197
+ packageName,
198
+ `ZORA component '${componentName}' is owned by both '${existingOwner}' and '${packageName}'.`,
199
+ componentName,
200
+ );
201
+ }
202
+ owners.set(componentName, packageName);
203
+ }
204
+
205
+ function applyPlacement(args: {
206
+ descriptor: ZoraPluginMetadata;
207
+ placement: ZoraPluginPlacement;
208
+ componentMeta: Map<string, ZoraComponentMeta>;
209
+ extensionHosts: ReadonlySet<string>;
210
+ }): void {
211
+ const { descriptor, placement, componentMeta, extensionHosts } = args;
212
+ const childMeta = descriptor.componentMeta[placement.child];
213
+ if (!childMeta?.directManifestNode) {
214
+ throw compositionError(
215
+ 'invalid-placement-child',
216
+ descriptor.packageName,
217
+ `ZORA plugin '${descriptor.packageName}' placement child '${placement.child}' is not an owned direct manifest node.`,
218
+ placement.child,
219
+ );
220
+ }
221
+ for (const parentName of [...placement.parents].sort()) {
222
+ const parentMeta = componentMeta.get(parentName);
223
+ if (!parentMeta || !extensionHosts.has(parentName)) {
224
+ throw compositionError(
225
+ 'invalid-placement-parent',
226
+ descriptor.packageName,
227
+ `ZORA plugin '${descriptor.packageName}' placement parent '${parentName}' is not a declared extension host.`,
228
+ placement.child,
229
+ );
230
+ }
231
+ componentMeta.set(parentName, addAllowedChild(parentMeta, placement.child));
232
+ }
233
+ }
234
+
235
+ function addAllowedChild(meta: ZoraComponentMeta, child: string): ZoraComponentMeta {
236
+ const allowedChildren = appendUnique(meta.allowedChildren, child);
237
+ const childrenSlot = meta.slots?.children;
238
+ return {
239
+ ...meta,
240
+ allowedChildren,
241
+ ...(childrenSlot
242
+ ? {
243
+ slots: {
244
+ ...meta.slots,
245
+ children: {
246
+ ...childrenSlot,
247
+ allowedChildren: appendUnique(childrenSlot.allowedChildren ?? [], child),
248
+ },
249
+ },
250
+ }
251
+ : {}),
252
+ };
253
+ }
254
+
255
+ function appendUnique(values: readonly string[], value: string): readonly string[] {
256
+ return values.includes(value) ? values : [...values, value];
257
+ }
258
+
259
+ function sortedKeys(value: Readonly<Record<string, unknown>>): string[] {
260
+ return Object.keys(value).sort();
261
+ }
262
+
263
+ function sortedEntries<T>(value: Readonly<Record<string, T>>): [string, T][] {
264
+ return Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
265
+ }
266
+
267
+ function sortPlugins<T extends ZoraPluginMetadata>(plugins: readonly T[]): T[] {
268
+ return [...plugins].sort((left, right) => left.packageName.localeCompare(right.packageName));
269
+ }
270
+
271
+ function compositionError(
272
+ code: ZoraPluginCompositionErrorCode,
273
+ packageName: string,
274
+ message: string,
275
+ componentName?: string,
276
+ ): ZoraPluginCompositionError {
277
+ return new ZoraPluginCompositionError({ code, packageName, message, componentName });
278
+ }