@lorion-org/provider-selection 1.0.0-beta.1 → 1.0.0-beta.5
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/README.md +109 -22
- package/dist/index.cjs +75 -11
- package/dist/index.d.cts +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.js +71 -10
- package/package.json +8 -3
- package/src/index.ts +355 -0
- package/examples/payment-checkout.ts +0 -38
- package/examples/provider-preferences.ts +0 -42
package/README.md
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
`@lorion-org/provider-selection` is a small framework-free core for selecting one
|
|
4
4
|
provider per capability from multiple candidates.
|
|
5
5
|
|
|
6
|
-
It solves
|
|
6
|
+
It solves these things:
|
|
7
7
|
|
|
8
8
|
- collect provider candidates by capability
|
|
9
|
+
- collect provider-owned defaults from descriptors
|
|
9
10
|
- collect provider preferences from descriptors or config-like records
|
|
11
|
+
- collect explicit provider preferences from selected provider ids
|
|
10
12
|
- optionally collect and resolve in one call
|
|
11
13
|
- pick one provider with configured and fallback preferences
|
|
12
14
|
- report misconfigured provider selections
|
|
@@ -15,19 +17,20 @@ It solves six things:
|
|
|
15
17
|
Selection order is always:
|
|
16
18
|
|
|
17
19
|
1. configured provider
|
|
18
|
-
2.
|
|
19
|
-
3.
|
|
20
|
+
2. explicitly selected provider
|
|
21
|
+
3. fallback provider
|
|
22
|
+
4. first provider in deterministic sort order
|
|
20
23
|
|
|
21
24
|
If a configured provider is set but not present among the candidates, the package
|
|
22
25
|
does not silently fall back. It reports a mismatch and leaves that capability
|
|
23
26
|
unselected.
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
Example descriptors in this repository:
|
|
26
29
|
|
|
27
|
-
- `
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
30
|
+
- `examples/nuxt/layer-extensions/payment-provider-stripe/extension.json`
|
|
31
|
+
- `examples/nuxt/layer-extensions/payment-provider-invoice/extension.json`
|
|
32
|
+
- `examples/react-runtime/capabilities/payment-provider-stripe/capability.json`
|
|
33
|
+
- `examples/react-runtime/capabilities/payment-provider-invoice/capability.json`
|
|
31
34
|
|
|
32
35
|
It does not know anything about:
|
|
33
36
|
|
|
@@ -45,19 +48,77 @@ pnpm add @lorion-org/provider-selection
|
|
|
45
48
|
|
|
46
49
|
## Example
|
|
47
50
|
|
|
51
|
+
Use provider-owned defaults when a provider descriptor should decide the normal
|
|
52
|
+
provider for a capability:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import {
|
|
56
|
+
collectProviderDefaults,
|
|
57
|
+
resolveItemProviderSelection,
|
|
58
|
+
} from '@lorion-org/provider-selection';
|
|
59
|
+
|
|
60
|
+
const descriptors = [
|
|
61
|
+
{ id: 'payment-provider-stripe', providesFor: 'checkout', defaultFor: 'checkout' },
|
|
62
|
+
{ id: 'payment-provider-invoice', providesFor: 'checkout' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const fallbackProviders = collectProviderDefaults({
|
|
66
|
+
items: descriptors,
|
|
67
|
+
getDefaultFor: (item) => item.defaultFor,
|
|
68
|
+
getProviderId: (item) => item.id,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const result = resolveItemProviderSelection({
|
|
72
|
+
items: descriptors,
|
|
73
|
+
getCapabilityId: (item) => item.providesFor,
|
|
74
|
+
getProviderId: (item) => item.id,
|
|
75
|
+
fallbackProviders,
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This selects Stripe with mode `fallback`. `providesFor` and `defaultFor` both
|
|
80
|
+
accept a string or string array, so one provider can serve multiple capabilities.
|
|
81
|
+
|
|
82
|
+
Use explicit selected providers for seed-owned overrides. This is useful when a
|
|
83
|
+
host app lets users select descriptors through a normal feature or profile seed,
|
|
84
|
+
and a selected descriptor is also a provider for a capability:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import {
|
|
88
|
+
collectSelectedProviderPreferences,
|
|
89
|
+
resolveItemProviderSelection,
|
|
90
|
+
} from '@lorion-org/provider-selection';
|
|
91
|
+
|
|
92
|
+
const selectedProviders = collectSelectedProviderPreferences({
|
|
93
|
+
items: descriptors,
|
|
94
|
+
getCapabilityId: (descriptor) => descriptor.providesFor,
|
|
95
|
+
getProviderId: (descriptor) => descriptor.id,
|
|
96
|
+
selectedProviderIds: ['payment-provider-invoice'],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = resolveItemProviderSelection({
|
|
100
|
+
items: descriptors,
|
|
101
|
+
getCapabilityId: (descriptor) => descriptor.providesFor,
|
|
102
|
+
getProviderId: (descriptor) => descriptor.id,
|
|
103
|
+
fallbackProviders,
|
|
104
|
+
selectedProviders,
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This selects Invoice with mode `selected`, even though Stripe owns the fallback
|
|
109
|
+
default. Configured providers still have higher priority.
|
|
110
|
+
|
|
111
|
+
Use configured providers for deployment-owned overrides:
|
|
112
|
+
|
|
48
113
|
```ts
|
|
49
114
|
import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
|
|
50
115
|
|
|
51
116
|
const result = resolveItemProviderSelection({
|
|
52
|
-
items:
|
|
53
|
-
{ providesFor: 'payment-checkout', id: 'payment-provider-stripe' },
|
|
54
|
-
{ providesFor: 'payment-checkout', id: 'payment-provider-invoice' },
|
|
55
|
-
],
|
|
117
|
+
items: descriptors,
|
|
56
118
|
getCapabilityId: (item) => item.providesFor,
|
|
57
119
|
getProviderId: (item) => item.id,
|
|
58
|
-
configuredProviders: {
|
|
59
|
-
|
|
60
|
-
},
|
|
120
|
+
configuredProviders: { checkout: 'payment-provider-invoice' },
|
|
121
|
+
fallbackProviders,
|
|
61
122
|
});
|
|
62
123
|
|
|
63
124
|
result.selections;
|
|
@@ -74,27 +135,34 @@ import { resolveProviderSelection } from '@lorion-org/provider-selection';
|
|
|
74
135
|
|
|
75
136
|
const result = resolveProviderSelection({
|
|
76
137
|
providersByCapability: new Map([
|
|
77
|
-
['
|
|
138
|
+
['checkout', ['payment-provider-invoice', 'payment-provider-stripe']],
|
|
78
139
|
]),
|
|
79
140
|
configuredProviders: {
|
|
80
|
-
|
|
141
|
+
checkout: 'missing-provider',
|
|
81
142
|
},
|
|
82
143
|
});
|
|
83
144
|
|
|
84
145
|
result.selections;
|
|
85
146
|
result.mismatches;
|
|
86
|
-
// => [{ capabilityId: '
|
|
147
|
+
// => [{ capabilityId: 'checkout', configuredProviderId: 'missing-provider' }]
|
|
87
148
|
```
|
|
88
149
|
|
|
89
|
-
If
|
|
150
|
+
If profile or descriptor preferences are stored on descriptors, collect them as
|
|
151
|
+
fallback preferences before resolving:
|
|
90
152
|
|
|
91
153
|
```ts
|
|
92
154
|
import {
|
|
155
|
+
collectProviderDefaults,
|
|
93
156
|
collectProviderPreferences,
|
|
94
157
|
resolveItemProviderSelection,
|
|
95
158
|
} from '@lorion-org/provider-selection';
|
|
96
159
|
|
|
97
|
-
const
|
|
160
|
+
const providerDefaults = collectProviderDefaults({
|
|
161
|
+
items: descriptors,
|
|
162
|
+
getDefaultFor: (descriptor) => descriptor.defaultFor,
|
|
163
|
+
getProviderId: (descriptor) => descriptor.id,
|
|
164
|
+
});
|
|
165
|
+
const descriptorPreferences = collectProviderPreferences({
|
|
98
166
|
items: descriptors,
|
|
99
167
|
getProviderPreferences: (descriptor) => descriptor.providerPreferences,
|
|
100
168
|
});
|
|
@@ -103,7 +171,10 @@ const result = resolveItemProviderSelection({
|
|
|
103
171
|
items: descriptors,
|
|
104
172
|
getCapabilityId: (descriptor) => descriptor.providesFor,
|
|
105
173
|
getProviderId: (descriptor) => descriptor.id,
|
|
106
|
-
|
|
174
|
+
fallbackProviders: {
|
|
175
|
+
...providerDefaults,
|
|
176
|
+
...descriptorPreferences,
|
|
177
|
+
},
|
|
107
178
|
});
|
|
108
179
|
```
|
|
109
180
|
|
|
@@ -112,9 +183,22 @@ const result = resolveItemProviderSelection({
|
|
|
112
183
|
```ts
|
|
113
184
|
type CapabilityId = string;
|
|
114
185
|
type ProviderId = string;
|
|
115
|
-
type ProviderSelectionMode = 'configured' | 'fallback' | 'first';
|
|
186
|
+
type ProviderSelectionMode = 'configured' | 'selected' | 'fallback' | 'first';
|
|
116
187
|
type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
|
|
117
188
|
type ProvidersByCapability = Map<CapabilityId, ProviderId[]>;
|
|
189
|
+
type ProviderCollectionInput<T> = {
|
|
190
|
+
items: Iterable<T>;
|
|
191
|
+
getCapabilityId: (item: T) => unknown;
|
|
192
|
+
getProviderId: (item: T) => ProviderId;
|
|
193
|
+
};
|
|
194
|
+
type SelectedProviderPreferenceCollectionInput<T> = ProviderCollectionInput<T> & {
|
|
195
|
+
selectedProviderIds: Iterable<ProviderId>;
|
|
196
|
+
};
|
|
197
|
+
type ProviderDefaultCollectionInput<T> = {
|
|
198
|
+
items: Iterable<T>;
|
|
199
|
+
getDefaultFor: (item: T) => unknown;
|
|
200
|
+
getProviderId: (item: T) => ProviderId;
|
|
201
|
+
};
|
|
118
202
|
|
|
119
203
|
type ProviderSelection = {
|
|
120
204
|
capabilityId: CapabilityId;
|
|
@@ -141,9 +225,12 @@ type ItemProviderSelectionResolution = ProviderSelectionResolution & {
|
|
|
141
225
|
|
|
142
226
|
The package exposes:
|
|
143
227
|
|
|
228
|
+
- `collectProviderDefaults()`
|
|
144
229
|
- `collectProviderPreferences()`
|
|
145
230
|
- `collectProvidersByCapability()`
|
|
231
|
+
- `collectSelectedProviderPreferences()`
|
|
146
232
|
- `resolveItemProviderSelection()`
|
|
233
|
+
- `resolveSelectedProviderRelationPreferences()`
|
|
147
234
|
- `resolveProviderSelection()`
|
|
148
235
|
|
|
149
236
|
## Local commands
|
package/dist/index.cjs
CHANGED
|
@@ -20,15 +20,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
collectProviderDefaults: () => collectProviderDefaults,
|
|
23
24
|
collectProviderPreferences: () => collectProviderPreferences,
|
|
24
25
|
collectProvidersByCapability: () => collectProvidersByCapability,
|
|
26
|
+
collectSelectedProviderPreferences: () => collectSelectedProviderPreferences,
|
|
25
27
|
resolveItemProviderSelection: () => resolveItemProviderSelection,
|
|
26
|
-
resolveProviderSelection: () => resolveProviderSelection
|
|
28
|
+
resolveProviderSelection: () => resolveProviderSelection,
|
|
29
|
+
resolveSelectedProviderRelationPreferences: () => resolveSelectedProviderRelationPreferences
|
|
27
30
|
});
|
|
28
31
|
module.exports = __toCommonJS(index_exports);
|
|
29
32
|
function toSortedUniqueProviderIds(providerIds) {
|
|
30
33
|
return Array.from(new Set(Array.from(providerIds).filter(Boolean))).sort();
|
|
31
34
|
}
|
|
35
|
+
function toCapabilityIds(value) {
|
|
36
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
37
|
+
return entries.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
38
|
+
}
|
|
32
39
|
function isRecord(value) {
|
|
33
40
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34
41
|
}
|
|
@@ -47,6 +54,13 @@ function getSelectedProvider(input) {
|
|
|
47
54
|
}
|
|
48
55
|
return void 0;
|
|
49
56
|
}
|
|
57
|
+
const selectedProviderId = input.selectedProviders?.[input.capabilityId];
|
|
58
|
+
if (selectedProviderId && input.candidateProviderIds.includes(selectedProviderId)) {
|
|
59
|
+
return {
|
|
60
|
+
selectedProviderId,
|
|
61
|
+
mode: "selected"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
50
64
|
const fallbackProviderId = input.fallbackProviders?.[input.capabilityId];
|
|
51
65
|
if (fallbackProviderId && input.candidateProviderIds.includes(fallbackProviderId)) {
|
|
52
66
|
return {
|
|
@@ -69,7 +83,8 @@ function selectProviders(input) {
|
|
|
69
83
|
capabilityId,
|
|
70
84
|
candidateProviderIds: normalizedCandidateProviderIds,
|
|
71
85
|
...input.configuredProviders ? { configuredProviders: input.configuredProviders } : {},
|
|
72
|
-
...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}
|
|
86
|
+
...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {},
|
|
87
|
+
...input.selectedProviders ? { selectedProviders: input.selectedProviders } : {}
|
|
73
88
|
});
|
|
74
89
|
if (!selected) {
|
|
75
90
|
continue;
|
|
@@ -124,14 +139,13 @@ function getExcludedProviders(selections) {
|
|
|
124
139
|
function collectProvidersByCapability(input) {
|
|
125
140
|
const providersByCapability = /* @__PURE__ */ new Map();
|
|
126
141
|
for (const item of input.items) {
|
|
127
|
-
const capabilityId = input.getCapabilityId(item);
|
|
128
|
-
if (!capabilityId) {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
142
|
const providerId = input.getProviderId(item);
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
const capabilityIds = toCapabilityIds(input.getCapabilityId(item));
|
|
144
|
+
for (const capabilityId of capabilityIds) {
|
|
145
|
+
const currentProviderIds = providersByCapability.get(capabilityId) ?? [];
|
|
146
|
+
currentProviderIds.push(providerId);
|
|
147
|
+
providersByCapability.set(capabilityId, toSortedUniqueProviderIds(currentProviderIds));
|
|
148
|
+
}
|
|
135
149
|
}
|
|
136
150
|
return providersByCapability;
|
|
137
151
|
}
|
|
@@ -151,6 +165,52 @@ function collectProviderPreferences(input) {
|
|
|
151
165
|
}
|
|
152
166
|
return preferences;
|
|
153
167
|
}
|
|
168
|
+
function normalizeDefaultFor(value) {
|
|
169
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
170
|
+
return entries.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
171
|
+
}
|
|
172
|
+
function collectProviderDefaults(input) {
|
|
173
|
+
let defaults = {};
|
|
174
|
+
for (const item of input.items) {
|
|
175
|
+
const providerId = input.getProviderId(item);
|
|
176
|
+
const capabilityIds = normalizeDefaultFor(input.getDefaultFor(item));
|
|
177
|
+
defaults = {
|
|
178
|
+
...defaults,
|
|
179
|
+
...Object.fromEntries(capabilityIds.map((capabilityId) => [capabilityId, providerId]))
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return defaults;
|
|
183
|
+
}
|
|
184
|
+
function collectSelectedProviderPreferences(input) {
|
|
185
|
+
const selectedProviderIds = new Set(input.selectedProviderIds);
|
|
186
|
+
const selectedItems = Array.from(input.items).filter((item) => selectedProviderIds.has(input.getProviderId(item))).sort((left, right) => input.getProviderId(left).localeCompare(input.getProviderId(right)));
|
|
187
|
+
const preferences = {};
|
|
188
|
+
for (const item of selectedItems) {
|
|
189
|
+
const providerId = input.getProviderId(item);
|
|
190
|
+
for (const capabilityId of toCapabilityIds(input.getCapabilityId(item))) {
|
|
191
|
+
if (!preferences[capabilityId]) {
|
|
192
|
+
preferences[capabilityId] = providerId;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return preferences;
|
|
197
|
+
}
|
|
198
|
+
function resolveSelectedProviderRelationPreferences(input) {
|
|
199
|
+
const selectedProviders = input.selectedProviders ?? {};
|
|
200
|
+
const selectedCapabilityIds = new Set(Object.keys(selectedProviders));
|
|
201
|
+
const defaultFor = toCapabilityIds(input.defaultFor).filter(
|
|
202
|
+
(capabilityId) => !selectedProviders[capabilityId] || selectedProviders[capabilityId] === input.providerId
|
|
203
|
+
);
|
|
204
|
+
const providerPreferences = Object.fromEntries(
|
|
205
|
+
Object.entries(input.providerPreferences ?? {}).filter(
|
|
206
|
+
([capabilityId]) => !selectedCapabilityIds.has(capabilityId)
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
return {
|
|
210
|
+
...defaultFor.length ? { defaultFor: Array.isArray(input.defaultFor) ? defaultFor : defaultFor[0] } : {},
|
|
211
|
+
...Object.keys(providerPreferences).length ? { providerPreferences } : {}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
154
214
|
function resolveProviderSelection(input) {
|
|
155
215
|
const selections = selectProviders(input);
|
|
156
216
|
const mismatches = findConfiguredProviderMismatches(input);
|
|
@@ -166,7 +226,8 @@ function resolveItemProviderSelection(input) {
|
|
|
166
226
|
const resolution = resolveProviderSelection({
|
|
167
227
|
providersByCapability,
|
|
168
228
|
...input.configuredProviders ? { configuredProviders: input.configuredProviders } : {},
|
|
169
|
-
...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}
|
|
229
|
+
...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {},
|
|
230
|
+
...input.selectedProviders ? { selectedProviders: input.selectedProviders } : {}
|
|
170
231
|
});
|
|
171
232
|
return {
|
|
172
233
|
providersByCapability,
|
|
@@ -175,8 +236,11 @@ function resolveItemProviderSelection(input) {
|
|
|
175
236
|
}
|
|
176
237
|
// Annotate the CommonJS export names for ESM import in node:
|
|
177
238
|
0 && (module.exports = {
|
|
239
|
+
collectProviderDefaults,
|
|
178
240
|
collectProviderPreferences,
|
|
179
241
|
collectProvidersByCapability,
|
|
242
|
+
collectSelectedProviderPreferences,
|
|
180
243
|
resolveItemProviderSelection,
|
|
181
|
-
resolveProviderSelection
|
|
244
|
+
resolveProviderSelection,
|
|
245
|
+
resolveSelectedProviderRelationPreferences
|
|
182
246
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
type CapabilityId = string;
|
|
2
2
|
type ProviderId = string;
|
|
3
|
-
type ProviderSelectionMode = 'configured' | 'fallback' | 'first';
|
|
3
|
+
type ProviderSelectionMode = 'configured' | 'selected' | 'fallback' | 'first';
|
|
4
4
|
type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
|
|
5
5
|
type ProviderPreferenceCollectionInput<T> = {
|
|
6
6
|
items: Iterable<T>;
|
|
7
7
|
getProviderPreferences: (item: T) => unknown;
|
|
8
8
|
};
|
|
9
|
+
type ProviderDefaultCollectionInput<T> = {
|
|
10
|
+
items: Iterable<T>;
|
|
11
|
+
getDefaultFor: (item: T) => unknown;
|
|
12
|
+
getProviderId: (item: T) => ProviderId;
|
|
13
|
+
};
|
|
14
|
+
type SelectedProviderPreferenceCollectionInput<T> = ProviderCollectionInput<T> & {
|
|
15
|
+
selectedProviderIds: Iterable<ProviderId>;
|
|
16
|
+
};
|
|
17
|
+
type SelectedProviderRelationPreferenceInput = {
|
|
18
|
+
defaultFor?: CapabilityId | CapabilityId[] | undefined;
|
|
19
|
+
providerId: ProviderId;
|
|
20
|
+
providerPreferences?: ProviderPreferenceMap | undefined;
|
|
21
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
22
|
+
};
|
|
23
|
+
type SelectedProviderRelationPreferences = {
|
|
24
|
+
defaultFor?: CapabilityId | CapabilityId[];
|
|
25
|
+
providerPreferences?: ProviderPreferenceMap;
|
|
26
|
+
};
|
|
9
27
|
type ProviderSelection = {
|
|
10
28
|
capabilityId: CapabilityId;
|
|
11
29
|
selectedProviderId: ProviderId;
|
|
@@ -27,21 +45,26 @@ type ItemProviderSelectionResolution = ProviderSelectionResolution & {
|
|
|
27
45
|
};
|
|
28
46
|
type ProviderCollectionInput<T> = {
|
|
29
47
|
items: Iterable<T>;
|
|
30
|
-
getCapabilityId: (item: T) => CapabilityId | undefined;
|
|
48
|
+
getCapabilityId: (item: T) => CapabilityId | CapabilityId[] | undefined;
|
|
31
49
|
getProviderId: (item: T) => ProviderId;
|
|
32
50
|
};
|
|
33
51
|
type ResolveProviderSelectionInput = {
|
|
34
52
|
providersByCapability: ProvidersByCapability;
|
|
35
53
|
configuredProviders?: ProviderPreferenceMap;
|
|
36
54
|
fallbackProviders?: ProviderPreferenceMap;
|
|
55
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
37
56
|
};
|
|
38
57
|
type ResolveItemProviderSelectionInput<T> = ProviderCollectionInput<T> & {
|
|
39
58
|
configuredProviders?: ProviderPreferenceMap;
|
|
40
59
|
fallbackProviders?: ProviderPreferenceMap;
|
|
60
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
41
61
|
};
|
|
42
62
|
declare function collectProvidersByCapability<T>(input: ProviderCollectionInput<T>): ProvidersByCapability;
|
|
43
63
|
declare function collectProviderPreferences<T>(input: ProviderPreferenceCollectionInput<T>): ProviderPreferenceMap;
|
|
64
|
+
declare function collectProviderDefaults<T>(input: ProviderDefaultCollectionInput<T>): ProviderPreferenceMap;
|
|
65
|
+
declare function collectSelectedProviderPreferences<T>(input: SelectedProviderPreferenceCollectionInput<T>): ProviderPreferenceMap;
|
|
66
|
+
declare function resolveSelectedProviderRelationPreferences(input: SelectedProviderRelationPreferenceInput): SelectedProviderRelationPreferences;
|
|
44
67
|
declare function resolveProviderSelection(input: ResolveProviderSelectionInput): ProviderSelectionResolution;
|
|
45
68
|
declare function resolveItemProviderSelection<T>(input: ResolveItemProviderSelectionInput<T>): ItemProviderSelectionResolution;
|
|
46
69
|
|
|
47
|
-
export { type CapabilityId, type ItemProviderSelectionResolution, type ProviderId, type ProviderMismatch, type ProviderPreferenceCollectionInput, type ProviderPreferenceMap, type ProviderSelection, type ProviderSelectionMode, type ProviderSelectionResolution, type ProvidersByCapability, type ResolveItemProviderSelectionInput, type ResolveProviderSelectionInput, collectProviderPreferences, collectProvidersByCapability, resolveItemProviderSelection, resolveProviderSelection };
|
|
70
|
+
export { type CapabilityId, type ItemProviderSelectionResolution, type ProviderDefaultCollectionInput, type ProviderId, type ProviderMismatch, type ProviderPreferenceCollectionInput, type ProviderPreferenceMap, type ProviderSelection, type ProviderSelectionMode, type ProviderSelectionResolution, type ProvidersByCapability, type ResolveItemProviderSelectionInput, type ResolveProviderSelectionInput, type SelectedProviderPreferenceCollectionInput, type SelectedProviderRelationPreferenceInput, type SelectedProviderRelationPreferences, collectProviderDefaults, collectProviderPreferences, collectProvidersByCapability, collectSelectedProviderPreferences, resolveItemProviderSelection, resolveProviderSelection, resolveSelectedProviderRelationPreferences };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
type CapabilityId = string;
|
|
2
2
|
type ProviderId = string;
|
|
3
|
-
type ProviderSelectionMode = 'configured' | 'fallback' | 'first';
|
|
3
|
+
type ProviderSelectionMode = 'configured' | 'selected' | 'fallback' | 'first';
|
|
4
4
|
type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
|
|
5
5
|
type ProviderPreferenceCollectionInput<T> = {
|
|
6
6
|
items: Iterable<T>;
|
|
7
7
|
getProviderPreferences: (item: T) => unknown;
|
|
8
8
|
};
|
|
9
|
+
type ProviderDefaultCollectionInput<T> = {
|
|
10
|
+
items: Iterable<T>;
|
|
11
|
+
getDefaultFor: (item: T) => unknown;
|
|
12
|
+
getProviderId: (item: T) => ProviderId;
|
|
13
|
+
};
|
|
14
|
+
type SelectedProviderPreferenceCollectionInput<T> = ProviderCollectionInput<T> & {
|
|
15
|
+
selectedProviderIds: Iterable<ProviderId>;
|
|
16
|
+
};
|
|
17
|
+
type SelectedProviderRelationPreferenceInput = {
|
|
18
|
+
defaultFor?: CapabilityId | CapabilityId[] | undefined;
|
|
19
|
+
providerId: ProviderId;
|
|
20
|
+
providerPreferences?: ProviderPreferenceMap | undefined;
|
|
21
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
22
|
+
};
|
|
23
|
+
type SelectedProviderRelationPreferences = {
|
|
24
|
+
defaultFor?: CapabilityId | CapabilityId[];
|
|
25
|
+
providerPreferences?: ProviderPreferenceMap;
|
|
26
|
+
};
|
|
9
27
|
type ProviderSelection = {
|
|
10
28
|
capabilityId: CapabilityId;
|
|
11
29
|
selectedProviderId: ProviderId;
|
|
@@ -27,21 +45,26 @@ type ItemProviderSelectionResolution = ProviderSelectionResolution & {
|
|
|
27
45
|
};
|
|
28
46
|
type ProviderCollectionInput<T> = {
|
|
29
47
|
items: Iterable<T>;
|
|
30
|
-
getCapabilityId: (item: T) => CapabilityId | undefined;
|
|
48
|
+
getCapabilityId: (item: T) => CapabilityId | CapabilityId[] | undefined;
|
|
31
49
|
getProviderId: (item: T) => ProviderId;
|
|
32
50
|
};
|
|
33
51
|
type ResolveProviderSelectionInput = {
|
|
34
52
|
providersByCapability: ProvidersByCapability;
|
|
35
53
|
configuredProviders?: ProviderPreferenceMap;
|
|
36
54
|
fallbackProviders?: ProviderPreferenceMap;
|
|
55
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
37
56
|
};
|
|
38
57
|
type ResolveItemProviderSelectionInput<T> = ProviderCollectionInput<T> & {
|
|
39
58
|
configuredProviders?: ProviderPreferenceMap;
|
|
40
59
|
fallbackProviders?: ProviderPreferenceMap;
|
|
60
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
41
61
|
};
|
|
42
62
|
declare function collectProvidersByCapability<T>(input: ProviderCollectionInput<T>): ProvidersByCapability;
|
|
43
63
|
declare function collectProviderPreferences<T>(input: ProviderPreferenceCollectionInput<T>): ProviderPreferenceMap;
|
|
64
|
+
declare function collectProviderDefaults<T>(input: ProviderDefaultCollectionInput<T>): ProviderPreferenceMap;
|
|
65
|
+
declare function collectSelectedProviderPreferences<T>(input: SelectedProviderPreferenceCollectionInput<T>): ProviderPreferenceMap;
|
|
66
|
+
declare function resolveSelectedProviderRelationPreferences(input: SelectedProviderRelationPreferenceInput): SelectedProviderRelationPreferences;
|
|
44
67
|
declare function resolveProviderSelection(input: ResolveProviderSelectionInput): ProviderSelectionResolution;
|
|
45
68
|
declare function resolveItemProviderSelection<T>(input: ResolveItemProviderSelectionInput<T>): ItemProviderSelectionResolution;
|
|
46
69
|
|
|
47
|
-
export { type CapabilityId, type ItemProviderSelectionResolution, type ProviderId, type ProviderMismatch, type ProviderPreferenceCollectionInput, type ProviderPreferenceMap, type ProviderSelection, type ProviderSelectionMode, type ProviderSelectionResolution, type ProvidersByCapability, type ResolveItemProviderSelectionInput, type ResolveProviderSelectionInput, collectProviderPreferences, collectProvidersByCapability, resolveItemProviderSelection, resolveProviderSelection };
|
|
70
|
+
export { type CapabilityId, type ItemProviderSelectionResolution, type ProviderDefaultCollectionInput, type ProviderId, type ProviderMismatch, type ProviderPreferenceCollectionInput, type ProviderPreferenceMap, type ProviderSelection, type ProviderSelectionMode, type ProviderSelectionResolution, type ProvidersByCapability, type ResolveItemProviderSelectionInput, type ResolveProviderSelectionInput, type SelectedProviderPreferenceCollectionInput, type SelectedProviderRelationPreferenceInput, type SelectedProviderRelationPreferences, collectProviderDefaults, collectProviderPreferences, collectProvidersByCapability, collectSelectedProviderPreferences, resolveItemProviderSelection, resolveProviderSelection, resolveSelectedProviderRelationPreferences };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
function toSortedUniqueProviderIds(providerIds) {
|
|
3
3
|
return Array.from(new Set(Array.from(providerIds).filter(Boolean))).sort();
|
|
4
4
|
}
|
|
5
|
+
function toCapabilityIds(value) {
|
|
6
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
7
|
+
return entries.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
8
|
+
}
|
|
5
9
|
function isRecord(value) {
|
|
6
10
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
11
|
}
|
|
@@ -20,6 +24,13 @@ function getSelectedProvider(input) {
|
|
|
20
24
|
}
|
|
21
25
|
return void 0;
|
|
22
26
|
}
|
|
27
|
+
const selectedProviderId = input.selectedProviders?.[input.capabilityId];
|
|
28
|
+
if (selectedProviderId && input.candidateProviderIds.includes(selectedProviderId)) {
|
|
29
|
+
return {
|
|
30
|
+
selectedProviderId,
|
|
31
|
+
mode: "selected"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
23
34
|
const fallbackProviderId = input.fallbackProviders?.[input.capabilityId];
|
|
24
35
|
if (fallbackProviderId && input.candidateProviderIds.includes(fallbackProviderId)) {
|
|
25
36
|
return {
|
|
@@ -42,7 +53,8 @@ function selectProviders(input) {
|
|
|
42
53
|
capabilityId,
|
|
43
54
|
candidateProviderIds: normalizedCandidateProviderIds,
|
|
44
55
|
...input.configuredProviders ? { configuredProviders: input.configuredProviders } : {},
|
|
45
|
-
...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}
|
|
56
|
+
...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {},
|
|
57
|
+
...input.selectedProviders ? { selectedProviders: input.selectedProviders } : {}
|
|
46
58
|
});
|
|
47
59
|
if (!selected) {
|
|
48
60
|
continue;
|
|
@@ -97,14 +109,13 @@ function getExcludedProviders(selections) {
|
|
|
97
109
|
function collectProvidersByCapability(input) {
|
|
98
110
|
const providersByCapability = /* @__PURE__ */ new Map();
|
|
99
111
|
for (const item of input.items) {
|
|
100
|
-
const capabilityId = input.getCapabilityId(item);
|
|
101
|
-
if (!capabilityId) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
112
|
const providerId = input.getProviderId(item);
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
const capabilityIds = toCapabilityIds(input.getCapabilityId(item));
|
|
114
|
+
for (const capabilityId of capabilityIds) {
|
|
115
|
+
const currentProviderIds = providersByCapability.get(capabilityId) ?? [];
|
|
116
|
+
currentProviderIds.push(providerId);
|
|
117
|
+
providersByCapability.set(capabilityId, toSortedUniqueProviderIds(currentProviderIds));
|
|
118
|
+
}
|
|
108
119
|
}
|
|
109
120
|
return providersByCapability;
|
|
110
121
|
}
|
|
@@ -124,6 +135,52 @@ function collectProviderPreferences(input) {
|
|
|
124
135
|
}
|
|
125
136
|
return preferences;
|
|
126
137
|
}
|
|
138
|
+
function normalizeDefaultFor(value) {
|
|
139
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
140
|
+
return entries.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
141
|
+
}
|
|
142
|
+
function collectProviderDefaults(input) {
|
|
143
|
+
let defaults = {};
|
|
144
|
+
for (const item of input.items) {
|
|
145
|
+
const providerId = input.getProviderId(item);
|
|
146
|
+
const capabilityIds = normalizeDefaultFor(input.getDefaultFor(item));
|
|
147
|
+
defaults = {
|
|
148
|
+
...defaults,
|
|
149
|
+
...Object.fromEntries(capabilityIds.map((capabilityId) => [capabilityId, providerId]))
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return defaults;
|
|
153
|
+
}
|
|
154
|
+
function collectSelectedProviderPreferences(input) {
|
|
155
|
+
const selectedProviderIds = new Set(input.selectedProviderIds);
|
|
156
|
+
const selectedItems = Array.from(input.items).filter((item) => selectedProviderIds.has(input.getProviderId(item))).sort((left, right) => input.getProviderId(left).localeCompare(input.getProviderId(right)));
|
|
157
|
+
const preferences = {};
|
|
158
|
+
for (const item of selectedItems) {
|
|
159
|
+
const providerId = input.getProviderId(item);
|
|
160
|
+
for (const capabilityId of toCapabilityIds(input.getCapabilityId(item))) {
|
|
161
|
+
if (!preferences[capabilityId]) {
|
|
162
|
+
preferences[capabilityId] = providerId;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return preferences;
|
|
167
|
+
}
|
|
168
|
+
function resolveSelectedProviderRelationPreferences(input) {
|
|
169
|
+
const selectedProviders = input.selectedProviders ?? {};
|
|
170
|
+
const selectedCapabilityIds = new Set(Object.keys(selectedProviders));
|
|
171
|
+
const defaultFor = toCapabilityIds(input.defaultFor).filter(
|
|
172
|
+
(capabilityId) => !selectedProviders[capabilityId] || selectedProviders[capabilityId] === input.providerId
|
|
173
|
+
);
|
|
174
|
+
const providerPreferences = Object.fromEntries(
|
|
175
|
+
Object.entries(input.providerPreferences ?? {}).filter(
|
|
176
|
+
([capabilityId]) => !selectedCapabilityIds.has(capabilityId)
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
return {
|
|
180
|
+
...defaultFor.length ? { defaultFor: Array.isArray(input.defaultFor) ? defaultFor : defaultFor[0] } : {},
|
|
181
|
+
...Object.keys(providerPreferences).length ? { providerPreferences } : {}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
127
184
|
function resolveProviderSelection(input) {
|
|
128
185
|
const selections = selectProviders(input);
|
|
129
186
|
const mismatches = findConfiguredProviderMismatches(input);
|
|
@@ -139,7 +196,8 @@ function resolveItemProviderSelection(input) {
|
|
|
139
196
|
const resolution = resolveProviderSelection({
|
|
140
197
|
providersByCapability,
|
|
141
198
|
...input.configuredProviders ? { configuredProviders: input.configuredProviders } : {},
|
|
142
|
-
...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}
|
|
199
|
+
...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {},
|
|
200
|
+
...input.selectedProviders ? { selectedProviders: input.selectedProviders } : {}
|
|
143
201
|
});
|
|
144
202
|
return {
|
|
145
203
|
providersByCapability,
|
|
@@ -147,8 +205,11 @@ function resolveItemProviderSelection(input) {
|
|
|
147
205
|
};
|
|
148
206
|
}
|
|
149
207
|
export {
|
|
208
|
+
collectProviderDefaults,
|
|
150
209
|
collectProviderPreferences,
|
|
151
210
|
collectProvidersByCapability,
|
|
211
|
+
collectSelectedProviderPreferences,
|
|
152
212
|
resolveItemProviderSelection,
|
|
153
|
-
resolveProviderSelection
|
|
213
|
+
resolveProviderSelection,
|
|
214
|
+
resolveSelectedProviderRelationPreferences
|
|
154
215
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lorion-org/provider-selection",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "Framework-free capability provider selection primitives.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"types": "./dist/index.d.ts",
|
|
23
23
|
"exports": {
|
|
24
24
|
".": {
|
|
25
|
+
"lorion-source": {
|
|
26
|
+
"types": "./src/index.ts",
|
|
27
|
+
"default": "./src/index.ts"
|
|
28
|
+
},
|
|
25
29
|
"import": {
|
|
26
30
|
"types": "./dist/index.d.ts",
|
|
27
31
|
"default": "./dist/index.js"
|
|
@@ -34,8 +38,9 @@
|
|
|
34
38
|
},
|
|
35
39
|
"files": [
|
|
36
40
|
"dist",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"src/**/*.ts",
|
|
43
|
+
"!src/**/*.spec.ts"
|
|
39
44
|
],
|
|
40
45
|
"keywords": [
|
|
41
46
|
"provider",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
export type CapabilityId = string;
|
|
2
|
+
export type ProviderId = string;
|
|
3
|
+
|
|
4
|
+
export type ProviderSelectionMode = 'configured' | 'selected' | 'fallback' | 'first';
|
|
5
|
+
export type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
|
|
6
|
+
export type ProviderPreferenceCollectionInput<T> = {
|
|
7
|
+
items: Iterable<T>;
|
|
8
|
+
getProviderPreferences: (item: T) => unknown;
|
|
9
|
+
};
|
|
10
|
+
export type ProviderDefaultCollectionInput<T> = {
|
|
11
|
+
items: Iterable<T>;
|
|
12
|
+
getDefaultFor: (item: T) => unknown;
|
|
13
|
+
getProviderId: (item: T) => ProviderId;
|
|
14
|
+
};
|
|
15
|
+
export type SelectedProviderPreferenceCollectionInput<T> = ProviderCollectionInput<T> & {
|
|
16
|
+
selectedProviderIds: Iterable<ProviderId>;
|
|
17
|
+
};
|
|
18
|
+
export type SelectedProviderRelationPreferenceInput = {
|
|
19
|
+
defaultFor?: CapabilityId | CapabilityId[] | undefined;
|
|
20
|
+
providerId: ProviderId;
|
|
21
|
+
providerPreferences?: ProviderPreferenceMap | undefined;
|
|
22
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type SelectedProviderRelationPreferences = {
|
|
26
|
+
defaultFor?: CapabilityId | CapabilityId[];
|
|
27
|
+
providerPreferences?: ProviderPreferenceMap;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ProviderSelection = {
|
|
31
|
+
capabilityId: CapabilityId;
|
|
32
|
+
selectedProviderId: ProviderId;
|
|
33
|
+
candidateProviderIds: ProviderId[];
|
|
34
|
+
mode: ProviderSelectionMode;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ProviderMismatch = {
|
|
38
|
+
capabilityId: CapabilityId;
|
|
39
|
+
configuredProviderId: ProviderId;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ProvidersByCapability = Map<CapabilityId, ProviderId[]>;
|
|
43
|
+
|
|
44
|
+
export type ProviderSelectionResolution = {
|
|
45
|
+
selections: Map<CapabilityId, ProviderSelection>;
|
|
46
|
+
mismatches: ProviderMismatch[];
|
|
47
|
+
excludedProviderIds: ProviderId[];
|
|
48
|
+
};
|
|
49
|
+
export type ItemProviderSelectionResolution = ProviderSelectionResolution & {
|
|
50
|
+
providersByCapability: ProvidersByCapability;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type ProviderCollectionInput<T> = {
|
|
54
|
+
items: Iterable<T>;
|
|
55
|
+
getCapabilityId: (item: T) => CapabilityId | CapabilityId[] | undefined;
|
|
56
|
+
getProviderId: (item: T) => ProviderId;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type ResolveProviderSelectionInput = {
|
|
60
|
+
providersByCapability: ProvidersByCapability;
|
|
61
|
+
configuredProviders?: ProviderPreferenceMap;
|
|
62
|
+
fallbackProviders?: ProviderPreferenceMap;
|
|
63
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type ResolveItemProviderSelectionInput<T> = ProviderCollectionInput<T> & {
|
|
67
|
+
configuredProviders?: ProviderPreferenceMap;
|
|
68
|
+
fallbackProviders?: ProviderPreferenceMap;
|
|
69
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function toSortedUniqueProviderIds(providerIds: Iterable<ProviderId>): ProviderId[] {
|
|
73
|
+
return Array.from(new Set(Array.from(providerIds).filter(Boolean))).sort();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function toCapabilityIds(value: CapabilityId | CapabilityId[] | undefined): CapabilityId[] {
|
|
77
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
78
|
+
|
|
79
|
+
return entries.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
83
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getSelectedProvider(input: {
|
|
87
|
+
capabilityId: CapabilityId;
|
|
88
|
+
candidateProviderIds: ProviderId[];
|
|
89
|
+
configuredProviders?: ProviderPreferenceMap;
|
|
90
|
+
fallbackProviders?: ProviderPreferenceMap;
|
|
91
|
+
selectedProviders?: ProviderPreferenceMap;
|
|
92
|
+
}): { selectedProviderId: ProviderId; mode: ProviderSelectionMode } | undefined {
|
|
93
|
+
const firstProviderId = input.candidateProviderIds[0];
|
|
94
|
+
if (!firstProviderId) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const configuredProviderId = input.configuredProviders?.[input.capabilityId];
|
|
99
|
+
if (configuredProviderId) {
|
|
100
|
+
if (input.candidateProviderIds.includes(configuredProviderId)) {
|
|
101
|
+
return {
|
|
102
|
+
selectedProviderId: configuredProviderId,
|
|
103
|
+
mode: 'configured',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const selectedProviderId = input.selectedProviders?.[input.capabilityId];
|
|
111
|
+
if (selectedProviderId && input.candidateProviderIds.includes(selectedProviderId)) {
|
|
112
|
+
return {
|
|
113
|
+
selectedProviderId,
|
|
114
|
+
mode: 'selected',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const fallbackProviderId = input.fallbackProviders?.[input.capabilityId];
|
|
119
|
+
if (fallbackProviderId && input.candidateProviderIds.includes(fallbackProviderId)) {
|
|
120
|
+
return {
|
|
121
|
+
selectedProviderId: fallbackProviderId,
|
|
122
|
+
mode: 'fallback',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
selectedProviderId: firstProviderId,
|
|
128
|
+
mode: 'first',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function selectProviders(
|
|
133
|
+
input: ResolveProviderSelectionInput,
|
|
134
|
+
): Map<CapabilityId, ProviderSelection> {
|
|
135
|
+
const selections: Map<CapabilityId, ProviderSelection> = new Map();
|
|
136
|
+
|
|
137
|
+
for (const [capabilityId, candidateProviderIds] of Array.from(
|
|
138
|
+
input.providersByCapability.entries(),
|
|
139
|
+
).sort(([left], [right]) => left.localeCompare(right))) {
|
|
140
|
+
const normalizedCandidateProviderIds = toSortedUniqueProviderIds(candidateProviderIds);
|
|
141
|
+
const selected = getSelectedProvider({
|
|
142
|
+
capabilityId,
|
|
143
|
+
candidateProviderIds: normalizedCandidateProviderIds,
|
|
144
|
+
...(input.configuredProviders ? { configuredProviders: input.configuredProviders } : {}),
|
|
145
|
+
...(input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}),
|
|
146
|
+
...(input.selectedProviders ? { selectedProviders: input.selectedProviders } : {}),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (!selected) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
selections.set(capabilityId, {
|
|
154
|
+
capabilityId,
|
|
155
|
+
selectedProviderId: selected.selectedProviderId,
|
|
156
|
+
candidateProviderIds: normalizedCandidateProviderIds,
|
|
157
|
+
mode: selected.mode,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return selections;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function findConfiguredProviderMismatches(
|
|
165
|
+
input: ResolveProviderSelectionInput,
|
|
166
|
+
): ProviderMismatch[] {
|
|
167
|
+
const mismatches: ProviderMismatch[] = [];
|
|
168
|
+
|
|
169
|
+
for (const [capabilityId, configuredProviderId] of Object.entries(
|
|
170
|
+
input.configuredProviders ?? {},
|
|
171
|
+
).sort(([left], [right]) => left.localeCompare(right))) {
|
|
172
|
+
if (!configuredProviderId) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const candidateProviderIds = toSortedUniqueProviderIds(
|
|
177
|
+
input.providersByCapability.get(capabilityId) ?? [],
|
|
178
|
+
);
|
|
179
|
+
if (!candidateProviderIds.length) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (candidateProviderIds.includes(configuredProviderId)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
mismatches.push({
|
|
188
|
+
capabilityId,
|
|
189
|
+
configuredProviderId,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return mismatches;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getExcludedProviders(selections: Iterable<ProviderSelection>): ProviderId[] {
|
|
197
|
+
const excludedProviderIds: ProviderId[] = [];
|
|
198
|
+
|
|
199
|
+
for (const selection of selections) {
|
|
200
|
+
if (selection.candidateProviderIds.length <= 1) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (const candidateProviderId of selection.candidateProviderIds) {
|
|
205
|
+
if (candidateProviderId !== selection.selectedProviderId) {
|
|
206
|
+
excludedProviderIds.push(candidateProviderId);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return toSortedUniqueProviderIds(excludedProviderIds);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function collectProvidersByCapability<T>(
|
|
215
|
+
input: ProviderCollectionInput<T>,
|
|
216
|
+
): ProvidersByCapability {
|
|
217
|
+
const providersByCapability: ProvidersByCapability = new Map();
|
|
218
|
+
|
|
219
|
+
for (const item of input.items) {
|
|
220
|
+
const providerId = input.getProviderId(item);
|
|
221
|
+
const capabilityIds = toCapabilityIds(input.getCapabilityId(item));
|
|
222
|
+
|
|
223
|
+
for (const capabilityId of capabilityIds) {
|
|
224
|
+
const currentProviderIds = providersByCapability.get(capabilityId) ?? [];
|
|
225
|
+
currentProviderIds.push(providerId);
|
|
226
|
+
providersByCapability.set(capabilityId, toSortedUniqueProviderIds(currentProviderIds));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return providersByCapability;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function collectProviderPreferences<T>(
|
|
234
|
+
input: ProviderPreferenceCollectionInput<T>,
|
|
235
|
+
): ProviderPreferenceMap {
|
|
236
|
+
let preferences: ProviderPreferenceMap = {};
|
|
237
|
+
|
|
238
|
+
for (const item of input.items) {
|
|
239
|
+
const value = input.getProviderPreferences(item);
|
|
240
|
+
|
|
241
|
+
if (!isRecord(value)) continue;
|
|
242
|
+
|
|
243
|
+
preferences = {
|
|
244
|
+
...preferences,
|
|
245
|
+
...Object.fromEntries(
|
|
246
|
+
Object.entries(value).filter(
|
|
247
|
+
(entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].length > 0,
|
|
248
|
+
),
|
|
249
|
+
),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return preferences;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizeDefaultFor(value: unknown): CapabilityId[] {
|
|
257
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
258
|
+
|
|
259
|
+
return entries.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function collectProviderDefaults<T>(
|
|
263
|
+
input: ProviderDefaultCollectionInput<T>,
|
|
264
|
+
): ProviderPreferenceMap {
|
|
265
|
+
let defaults: ProviderPreferenceMap = {};
|
|
266
|
+
|
|
267
|
+
for (const item of input.items) {
|
|
268
|
+
const providerId = input.getProviderId(item);
|
|
269
|
+
const capabilityIds = normalizeDefaultFor(input.getDefaultFor(item));
|
|
270
|
+
|
|
271
|
+
defaults = {
|
|
272
|
+
...defaults,
|
|
273
|
+
...Object.fromEntries(capabilityIds.map((capabilityId) => [capabilityId, providerId])),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return defaults;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function collectSelectedProviderPreferences<T>(
|
|
281
|
+
input: SelectedProviderPreferenceCollectionInput<T>,
|
|
282
|
+
): ProviderPreferenceMap {
|
|
283
|
+
const selectedProviderIds = new Set(input.selectedProviderIds);
|
|
284
|
+
const selectedItems = Array.from(input.items)
|
|
285
|
+
.filter((item) => selectedProviderIds.has(input.getProviderId(item)))
|
|
286
|
+
.sort((left, right) => input.getProviderId(left).localeCompare(input.getProviderId(right)));
|
|
287
|
+
const preferences: ProviderPreferenceMap = {};
|
|
288
|
+
|
|
289
|
+
for (const item of selectedItems) {
|
|
290
|
+
const providerId = input.getProviderId(item);
|
|
291
|
+
|
|
292
|
+
for (const capabilityId of toCapabilityIds(input.getCapabilityId(item))) {
|
|
293
|
+
if (!preferences[capabilityId]) {
|
|
294
|
+
preferences[capabilityId] = providerId;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return preferences;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function resolveSelectedProviderRelationPreferences(
|
|
303
|
+
input: SelectedProviderRelationPreferenceInput,
|
|
304
|
+
): SelectedProviderRelationPreferences {
|
|
305
|
+
const selectedProviders = input.selectedProviders ?? {};
|
|
306
|
+
const selectedCapabilityIds = new Set(Object.keys(selectedProviders));
|
|
307
|
+
const defaultFor = toCapabilityIds(input.defaultFor).filter(
|
|
308
|
+
(capabilityId) =>
|
|
309
|
+
!selectedProviders[capabilityId] || selectedProviders[capabilityId] === input.providerId,
|
|
310
|
+
);
|
|
311
|
+
const providerPreferences = Object.fromEntries(
|
|
312
|
+
Object.entries(input.providerPreferences ?? {}).filter(
|
|
313
|
+
([capabilityId]) => !selectedCapabilityIds.has(capabilityId),
|
|
314
|
+
),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
...(defaultFor.length
|
|
319
|
+
? { defaultFor: Array.isArray(input.defaultFor) ? defaultFor : defaultFor[0] }
|
|
320
|
+
: {}),
|
|
321
|
+
...(Object.keys(providerPreferences).length ? { providerPreferences } : {}),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function resolveProviderSelection(
|
|
326
|
+
input: ResolveProviderSelectionInput,
|
|
327
|
+
): ProviderSelectionResolution {
|
|
328
|
+
const selections = selectProviders(input);
|
|
329
|
+
const mismatches = findConfiguredProviderMismatches(input);
|
|
330
|
+
const excludedProviderIds = getExcludedProviders(selections.values());
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
selections,
|
|
334
|
+
mismatches,
|
|
335
|
+
excludedProviderIds,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function resolveItemProviderSelection<T>(
|
|
340
|
+
input: ResolveItemProviderSelectionInput<T>,
|
|
341
|
+
): ItemProviderSelectionResolution {
|
|
342
|
+
const providersByCapability = collectProvidersByCapability(input);
|
|
343
|
+
|
|
344
|
+
const resolution = resolveProviderSelection({
|
|
345
|
+
providersByCapability,
|
|
346
|
+
...(input.configuredProviders ? { configuredProviders: input.configuredProviders } : {}),
|
|
347
|
+
...(input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}),
|
|
348
|
+
...(input.selectedProviders ? { selectedProviders: input.selectedProviders } : {}),
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
providersByCapability,
|
|
353
|
+
...resolution,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
|
|
2
|
-
|
|
3
|
-
type PaymentProviderDescriptor = {
|
|
4
|
-
id: string;
|
|
5
|
-
providesFor?: string;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
const descriptors: PaymentProviderDescriptor[] = [
|
|
9
|
-
{
|
|
10
|
-
id: 'payment-provider-stripe',
|
|
11
|
-
providesFor: 'payment-checkout',
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
id: 'payment-provider-invoice',
|
|
15
|
-
providesFor: 'payment-checkout',
|
|
16
|
-
},
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const result = resolveItemProviderSelection({
|
|
20
|
-
items: descriptors,
|
|
21
|
-
getCapabilityId: (descriptor) => descriptor.providesFor,
|
|
22
|
-
getProviderId: (descriptor) => descriptor.id,
|
|
23
|
-
configuredProviders: {
|
|
24
|
-
'payment-checkout': 'payment-provider-stripe',
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
console.log(result.providersByCapability);
|
|
29
|
-
// Map { 'payment-checkout' => ['payment-provider-invoice', 'payment-provider-stripe'] }
|
|
30
|
-
|
|
31
|
-
console.log(result.selections);
|
|
32
|
-
// Map { 'payment-checkout' => { selectedProviderId: 'payment-provider-stripe', mode: 'configured', ... } }
|
|
33
|
-
|
|
34
|
-
console.log(result.mismatches);
|
|
35
|
-
// []
|
|
36
|
-
|
|
37
|
-
console.log(result.excludedProviderIds);
|
|
38
|
-
// ['payment-provider-invoice']
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collectProviderPreferences,
|
|
3
|
-
resolveItemProviderSelection,
|
|
4
|
-
} from '@lorion-org/provider-selection';
|
|
5
|
-
|
|
6
|
-
type PlaygroundDescriptor = {
|
|
7
|
-
id: string;
|
|
8
|
-
providerPreferences?: Record<string, string>;
|
|
9
|
-
providesFor?: string;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const descriptors: PlaygroundDescriptor[] = [
|
|
13
|
-
{
|
|
14
|
-
id: 'web',
|
|
15
|
-
providerPreferences: {
|
|
16
|
-
'payment-checkout': 'payment-provider-stripe',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
id: 'payment-provider-stripe',
|
|
21
|
-
providesFor: 'payment-checkout',
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: 'payment-provider-invoice',
|
|
25
|
-
providesFor: 'payment-checkout',
|
|
26
|
-
},
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const configuredProviders = collectProviderPreferences({
|
|
30
|
-
items: descriptors,
|
|
31
|
-
getProviderPreferences: (descriptor) => descriptor.providerPreferences,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const result = resolveItemProviderSelection({
|
|
35
|
-
items: descriptors,
|
|
36
|
-
getCapabilityId: (descriptor) => descriptor.providesFor,
|
|
37
|
-
getProviderId: (descriptor) => descriptor.id,
|
|
38
|
-
configuredProviders,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
console.log(Object.fromEntries(result.selections));
|
|
42
|
-
// { 'payment-checkout': { selectedProviderId: 'payment-provider-stripe', mode: 'configured', ... } }
|