@lorion-org/react 1.0.0-beta.1 → 1.0.0-beta.3

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/src/index.ts ADDED
@@ -0,0 +1,225 @@
1
+ import { createContext, createElement, useContext, type ReactElement, type ReactNode } from 'react';
2
+ import {
3
+ assertKnownDescriptorIds,
4
+ createDescriptorCatalog,
5
+ type Descriptor,
6
+ type DescriptorCatalog,
7
+ } from '@lorion-org/composition-graph';
8
+ import {
9
+ collectProviderDefaults,
10
+ collectProviderPreferences,
11
+ resolveItemProviderSelection,
12
+ type ProviderPreferenceMap,
13
+ type ProviderSelectionResolution,
14
+ } from '@lorion-org/provider-selection';
15
+ import type { RuntimeConfigValidationPolicy } from '@lorion-org/runtime-config';
16
+ import { defaultCapabilityRelationDescriptors } from './relations';
17
+
18
+ export {
19
+ createCapabilityCompositionPolicy,
20
+ defaultCapabilityRelationDescriptors,
21
+ defaultCapabilityResolutionRelations,
22
+ } from './relations';
23
+ export * from './runtime-config';
24
+
25
+ export type CapabilityManifest = Descriptor & {
26
+ defaultFor?: string | string[];
27
+ description?: string;
28
+ providerPreferences?: ProviderPreferenceMap;
29
+ runtimeConfig?: RuntimeConfigValidationPolicy;
30
+ };
31
+
32
+ export type ExtensionPoint<T> = {
33
+ id: string;
34
+ readonly value?: T;
35
+ };
36
+
37
+ export type CapabilityContribution<T = unknown> = {
38
+ extensionPoint: ExtensionPoint<T>;
39
+ values: readonly T[];
40
+ };
41
+
42
+ export type RuntimeCapability = {
43
+ id: string;
44
+ manifest: CapabilityManifest;
45
+ contributions?: readonly CapabilityContribution[];
46
+ };
47
+
48
+ export type CapabilityRuntime = {
49
+ capabilities: readonly RuntimeCapability[];
50
+ catalog: DescriptorCatalog;
51
+ getContributions: <T>(extensionPoint: ExtensionPoint<T>) => T[];
52
+ };
53
+
54
+ export type ContributionContract<T> = {
55
+ define: (values: readonly T[]) => CapabilityContribution<T>;
56
+ extensionPoint: ExtensionPoint<T>;
57
+ get: (runtime: CapabilityRuntime) => T[];
58
+ use: () => T[];
59
+ };
60
+
61
+ export function defineExtensionPoint<T>(id: string): ExtensionPoint<T> {
62
+ return { id };
63
+ }
64
+
65
+ export function defineContribution<T>(
66
+ extensionPoint: ExtensionPoint<T>,
67
+ values: readonly T[],
68
+ ): CapabilityContribution<T> {
69
+ return { extensionPoint, values };
70
+ }
71
+
72
+ export function createContributionContract<T>(id: string): ContributionContract<T> {
73
+ const extensionPoint = defineExtensionPoint<T>(id);
74
+
75
+ return {
76
+ extensionPoint,
77
+ define: (values) => defineContribution(extensionPoint, values),
78
+ get: (runtime) => runtime.getContributions(extensionPoint),
79
+ use: () => useCapabilityRuntime().getContributions(extensionPoint),
80
+ };
81
+ }
82
+
83
+ export type CapabilityProviderSelectionOptions = {
84
+ configuredProviders?: ProviderPreferenceMap;
85
+ fallbackProviders?: ProviderPreferenceMap;
86
+ selectedProviders?: ProviderPreferenceMap;
87
+ };
88
+
89
+ export function getCapabilityProviderSelection(
90
+ runtime: CapabilityRuntime,
91
+ options: CapabilityProviderSelectionOptions = {},
92
+ ): ProviderSelectionResolution {
93
+ const descriptors = runtime.catalog.getAllDescriptors();
94
+ const descriptorPreferences = collectProviderPreferences({
95
+ items: descriptors,
96
+ getProviderPreferences: (descriptor) => descriptor.providerPreferences,
97
+ });
98
+ const providerDefaults = collectProviderDefaults({
99
+ items: descriptors,
100
+ getDefaultFor: (descriptor) => descriptor.defaultFor,
101
+ getProviderId: (descriptor) => descriptor.id,
102
+ });
103
+ const configuredProviders = options.configuredProviders ?? {};
104
+ const selectedProviders = options.selectedProviders ?? {};
105
+ const fallbackProviders = {
106
+ ...providerDefaults,
107
+ ...descriptorPreferences,
108
+ ...(options.fallbackProviders ?? {}),
109
+ };
110
+ const resolution = resolveItemProviderSelection({
111
+ items: descriptors,
112
+ getCapabilityId: (descriptor) => descriptor.providesFor,
113
+ getProviderId: (descriptor) => descriptor.id,
114
+ configuredProviders,
115
+ fallbackProviders,
116
+ selectedProviders,
117
+ });
118
+
119
+ return {
120
+ excludedProviderIds: resolution.excludedProviderIds,
121
+ mismatches: resolution.mismatches,
122
+ selections: resolution.selections,
123
+ };
124
+ }
125
+
126
+ export function defineCapability(capability: RuntimeCapability): RuntimeCapability {
127
+ if (capability.id !== capability.manifest.id) {
128
+ throw new Error(
129
+ `Capability "${capability.id}" must match capability.json id "${capability.manifest.id}".`,
130
+ );
131
+ }
132
+
133
+ return capability;
134
+ }
135
+
136
+ export function createCapabilityRuntime(
137
+ capabilities: readonly RuntimeCapability[],
138
+ ): CapabilityRuntime {
139
+ const enabledCapabilities = capabilities.filter(
140
+ (capability) => capability.manifest.disabled !== true,
141
+ );
142
+ const catalog = createCapabilityCatalog(enabledCapabilities);
143
+
144
+ assertUniqueCapabilities(enabledCapabilities);
145
+ assertKnownCapabilityDependencies(enabledCapabilities, catalog);
146
+
147
+ const contributions = collectContributions(enabledCapabilities);
148
+
149
+ return {
150
+ capabilities: Object.freeze([...enabledCapabilities]),
151
+ catalog,
152
+ getContributions: <T>(extensionPoint: ExtensionPoint<T>) =>
153
+ [...(contributions.get(extensionPoint.id) ?? [])] as T[],
154
+ };
155
+ }
156
+
157
+ const CapabilityRuntimeContext = createContext<CapabilityRuntime | null>(null);
158
+
159
+ export type CapabilityRuntimeProviderProps = {
160
+ children: ReactNode;
161
+ runtime: CapabilityRuntime;
162
+ };
163
+
164
+ export function CapabilityRuntimeProvider({
165
+ children,
166
+ runtime,
167
+ }: CapabilityRuntimeProviderProps): ReactElement {
168
+ return createElement(CapabilityRuntimeContext.Provider, { value: runtime }, children);
169
+ }
170
+
171
+ export function useCapabilityRuntime(): CapabilityRuntime {
172
+ const runtime = useContext(CapabilityRuntimeContext);
173
+
174
+ if (!runtime) {
175
+ throw new Error('useCapabilityRuntime must be used inside CapabilityRuntimeProvider.');
176
+ }
177
+
178
+ return runtime;
179
+ }
180
+
181
+ function assertUniqueCapabilities(capabilities: readonly RuntimeCapability[]): void {
182
+ const seen = new Set<string>();
183
+
184
+ for (const capability of capabilities) {
185
+ if (seen.has(capability.id)) {
186
+ throw new Error(`Duplicate capability id "${capability.id}".`);
187
+ }
188
+
189
+ seen.add(capability.id);
190
+ }
191
+ }
192
+
193
+ function assertKnownCapabilityDependencies(
194
+ capabilities: readonly RuntimeCapability[],
195
+ catalog: DescriptorCatalog,
196
+ ): void {
197
+ for (const capability of capabilities) {
198
+ assertKnownDescriptorIds(
199
+ catalog.getDescriptorMap(),
200
+ Object.keys(capability.manifest.dependencies ?? {}),
201
+ `capability "${capability.id}" dependencies`,
202
+ );
203
+ }
204
+ }
205
+
206
+ function collectContributions(capabilities: readonly RuntimeCapability[]): Map<string, unknown[]> {
207
+ const contributions = new Map<string, unknown[]>();
208
+
209
+ for (const capability of capabilities) {
210
+ for (const contribution of capability.contributions ?? []) {
211
+ const bucket = contributions.get(contribution.extensionPoint.id) ?? [];
212
+ bucket.push(...contribution.values);
213
+ contributions.set(contribution.extensionPoint.id, bucket);
214
+ }
215
+ }
216
+
217
+ return contributions;
218
+ }
219
+
220
+ function createCapabilityCatalog(capabilities: readonly RuntimeCapability[]): DescriptorCatalog {
221
+ return createDescriptorCatalog({
222
+ descriptors: capabilities.map((capability) => capability.manifest),
223
+ relationDescriptors: defaultCapabilityRelationDescriptors,
224
+ });
225
+ }
@@ -0,0 +1,8 @@
1
+ // The capability relations and composition policy are owned by
2
+ // @lorion-org/descriptor-selection. They are re-exported here under the React
3
+ // adapter's public names for backward compatibility; no logic is duplicated.
4
+ export {
5
+ providerRelationDescriptors as defaultCapabilityRelationDescriptors,
6
+ descriptorSelectionPolicy as createCapabilityCompositionPolicy,
7
+ defaultResolutionRelations as defaultCapabilityResolutionRelations,
8
+ } from '@lorion-org/descriptor-selection';
@@ -0,0 +1,63 @@
1
+ import { createContext, createElement, useContext, type ReactElement, type ReactNode } from 'react';
2
+ import type { RuntimeConfigSection } from '@lorion-org/runtime-config';
3
+
4
+ export type CapabilityRuntimeConfig = {
5
+ public: Record<string, RuntimeConfigSection>;
6
+ };
7
+
8
+ export type CapabilityRuntimeConfigFragment<T extends RuntimeConfigSection = RuntimeConfigSection> =
9
+ {
10
+ public: T;
11
+ };
12
+
13
+ export type CapabilityRuntimeConfigProviderProps = {
14
+ children: ReactNode;
15
+ runtimeConfig: CapabilityRuntimeConfig;
16
+ };
17
+
18
+ const emptyRuntimeConfig: CapabilityRuntimeConfig = Object.freeze({ public: Object.freeze({}) });
19
+ const CapabilityRuntimeConfigContext = createContext<CapabilityRuntimeConfig>(emptyRuntimeConfig);
20
+
21
+ export function CapabilityRuntimeConfigProvider({
22
+ children,
23
+ runtimeConfig,
24
+ }: CapabilityRuntimeConfigProviderProps): ReactElement {
25
+ return createElement(CapabilityRuntimeConfigContext.Provider, { value: runtimeConfig }, children);
26
+ }
27
+
28
+ export function useCapabilityRuntimeConfigScope<
29
+ T extends RuntimeConfigSection = RuntimeConfigSection,
30
+ >(capabilityId: string): T {
31
+ return getCapabilityRuntimeConfigScope<T>(useCapabilityRuntimeConfigRoot(), capabilityId);
32
+ }
33
+
34
+ export function useCapabilityRuntimeConfig<T extends RuntimeConfigSection = RuntimeConfigSection>(
35
+ capabilityId: string,
36
+ ): CapabilityRuntimeConfigFragment<T> {
37
+ return getCapabilityRuntimeConfig<T>(useCapabilityRuntimeConfigRoot(), capabilityId);
38
+ }
39
+
40
+ export function useCapabilityRuntimeConfigRoot(): CapabilityRuntimeConfig {
41
+ return useContext(CapabilityRuntimeConfigContext);
42
+ }
43
+
44
+ export function getCapabilityRuntimeConfigScope<
45
+ T extends RuntimeConfigSection = RuntimeConfigSection,
46
+ >(runtimeConfig: CapabilityRuntimeConfig, capabilityId: string): T {
47
+ const publicConfig = runtimeConfig.public[capabilityId];
48
+
49
+ return (
50
+ publicConfig && typeof publicConfig === 'object' && !Array.isArray(publicConfig)
51
+ ? publicConfig
52
+ : {}
53
+ ) as T;
54
+ }
55
+
56
+ export function getCapabilityRuntimeConfig<T extends RuntimeConfigSection = RuntimeConfigSection>(
57
+ runtimeConfig: CapabilityRuntimeConfig,
58
+ capabilityId: string,
59
+ ): CapabilityRuntimeConfigFragment<T> {
60
+ return {
61
+ public: getCapabilityRuntimeConfigScope<T>(runtimeConfig, capabilityId),
62
+ };
63
+ }