@lorion-org/provider-selection 1.0.0-beta.0 → 1.0.0-beta.1

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the 'Software'), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the 'Software'), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -3,9 +3,10 @@
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 four things:
6
+ It solves six things:
7
7
 
8
8
  - collect provider candidates by capability
9
+ - collect provider preferences from descriptors or config-like records
9
10
  - optionally collect and resolve in one call
10
11
  - pick one provider with configured and fallback preferences
11
12
  - report misconfigured provider selections
@@ -21,10 +22,12 @@ If a configured provider is set but not present among the candidates, the packag
21
22
  does not silently fall back. It reports a mismatch and leaves that capability
22
23
  unselected.
23
24
 
24
- Example files in this repository:
25
+ Playground examples in this repository:
25
26
 
26
- - `examples/command-handlers.ts`
27
- - `examples/storage-drivers.ts`
27
+ - `packages/nuxt/playground/layer-extensions/payment-provider-stripe/extension.json`
28
+ - `packages/nuxt/playground/layer-extensions/payment-provider-invoice/extension.json`
29
+ - `packages/react/playground/capabilities/payment-provider-stripe/capability.json`
30
+ - `packages/react/playground/capabilities/payment-provider-invoice/capability.json`
28
31
 
29
32
  It does not know anything about:
30
33
 
@@ -47,17 +50,13 @@ import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
47
50
 
48
51
  const result = resolveItemProviderSelection({
49
52
  items: [
50
- { capability: 'auth', providerId: 'keycloak' },
51
- { capability: 'auth', providerId: 'auth-local-jwt' },
52
- { capability: 'mailer', providerId: 'mailer-postmark' },
53
+ { providesFor: 'payment-checkout', id: 'payment-provider-stripe' },
54
+ { providesFor: 'payment-checkout', id: 'payment-provider-invoice' },
53
55
  ],
54
- getCapabilityId: (item) => item.capability,
55
- getProviderId: (item) => item.providerId,
56
+ getCapabilityId: (item) => item.providesFor,
57
+ getProviderId: (item) => item.id,
56
58
  configuredProviders: {
57
- auth: 'keycloak',
58
- },
59
- fallbackProviders: {
60
- mailer: 'mailer-postmark',
59
+ 'payment-checkout': 'payment-provider-stripe',
61
60
  },
62
61
  });
63
62
 
@@ -74,52 +73,37 @@ resolver directly:
74
73
  import { resolveProviderSelection } from '@lorion-org/provider-selection';
75
74
 
76
75
  const result = resolveProviderSelection({
77
- providersByCapability: new Map([['auth', ['auth-local-jwt', 'keycloak']]]),
76
+ providersByCapability: new Map([
77
+ ['payment-checkout', ['payment-provider-invoice', 'payment-provider-stripe']],
78
+ ]),
78
79
  configuredProviders: {
79
- auth: 'missing-provider',
80
+ 'payment-checkout': 'missing-provider',
80
81
  },
81
82
  });
82
83
 
83
84
  result.selections;
84
85
  result.mismatches;
85
- // => [{ capabilityId: 'auth', configuredProviderId: 'missing-provider' }]
86
+ // => [{ capabilityId: 'payment-checkout', configuredProviderId: 'missing-provider' }]
86
87
  ```
87
88
 
88
- ## Example: command handlers
89
+ If provider preferences are stored on descriptors, collect them before resolving:
89
90
 
90
91
  ```ts
91
- import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
92
-
93
- const result = resolveItemProviderSelection({
94
- items: [
95
- { commandId: 'open', handlerId: 'open-native' },
96
- { commandId: 'open', handlerId: 'open-web' },
97
- { commandId: 'share', handlerId: 'share-link' },
98
- ],
99
- getCapabilityId: (item) => item.commandId,
100
- getProviderId: (item) => item.handlerId,
101
- configuredProviders: {
102
- open: 'open-web',
103
- },
92
+ import {
93
+ collectProviderPreferences,
94
+ resolveItemProviderSelection,
95
+ } from '@lorion-org/provider-selection';
96
+
97
+ const configuredProviders = collectProviderPreferences({
98
+ items: descriptors,
99
+ getProviderPreferences: (descriptor) => descriptor.providerPreferences,
104
100
  });
105
- ```
106
-
107
- ## Example: storage drivers
108
-
109
- ```ts
110
- import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
111
101
 
112
102
  const result = resolveItemProviderSelection({
113
- items: [
114
- { storageKind: 'blob', driverId: 's3' },
115
- { storageKind: 'blob', driverId: 'filesystem' },
116
- { storageKind: 'queue', driverId: 'redis-streams' },
117
- ],
118
- getCapabilityId: (item) => item.storageKind,
119
- getProviderId: (item) => item.driverId,
120
- fallbackProviders: {
121
- blob: 'filesystem',
122
- },
103
+ items: descriptors,
104
+ getCapabilityId: (descriptor) => descriptor.providesFor,
105
+ getProviderId: (descriptor) => descriptor.id,
106
+ configuredProviders,
123
107
  });
124
108
  ```
125
109
 
@@ -157,6 +141,7 @@ type ItemProviderSelectionResolution = ProviderSelectionResolution & {
157
141
 
158
142
  The package exposes:
159
143
 
144
+ - `collectProviderPreferences()`
160
145
  - `collectProvidersByCapability()`
161
146
  - `resolveItemProviderSelection()`
162
147
  - `resolveProviderSelection()`
package/dist/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ collectProviderPreferences: () => collectProviderPreferences,
23
24
  collectProvidersByCapability: () => collectProvidersByCapability,
24
25
  resolveItemProviderSelection: () => resolveItemProviderSelection,
25
26
  resolveProviderSelection: () => resolveProviderSelection
@@ -28,6 +29,9 @@ module.exports = __toCommonJS(index_exports);
28
29
  function toSortedUniqueProviderIds(providerIds) {
29
30
  return Array.from(new Set(Array.from(providerIds).filter(Boolean))).sort();
30
31
  }
32
+ function isRecord(value) {
33
+ return typeof value === "object" && value !== null && !Array.isArray(value);
34
+ }
31
35
  function getSelectedProvider(input) {
32
36
  const firstProviderId = input.candidateProviderIds[0];
33
37
  if (!firstProviderId) {
@@ -131,6 +135,22 @@ function collectProvidersByCapability(input) {
131
135
  }
132
136
  return providersByCapability;
133
137
  }
138
+ function collectProviderPreferences(input) {
139
+ let preferences = {};
140
+ for (const item of input.items) {
141
+ const value = input.getProviderPreferences(item);
142
+ if (!isRecord(value)) continue;
143
+ preferences = {
144
+ ...preferences,
145
+ ...Object.fromEntries(
146
+ Object.entries(value).filter(
147
+ (entry) => typeof entry[1] === "string" && entry[1].length > 0
148
+ )
149
+ )
150
+ };
151
+ }
152
+ return preferences;
153
+ }
134
154
  function resolveProviderSelection(input) {
135
155
  const selections = selectProviders(input);
136
156
  const mismatches = findConfiguredProviderMismatches(input);
@@ -155,6 +175,7 @@ function resolveItemProviderSelection(input) {
155
175
  }
156
176
  // Annotate the CommonJS export names for ESM import in node:
157
177
  0 && (module.exports = {
178
+ collectProviderPreferences,
158
179
  collectProvidersByCapability,
159
180
  resolveItemProviderSelection,
160
181
  resolveProviderSelection
package/dist/index.d.cts CHANGED
@@ -2,6 +2,10 @@ type CapabilityId = string;
2
2
  type ProviderId = string;
3
3
  type ProviderSelectionMode = 'configured' | 'fallback' | 'first';
4
4
  type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
5
+ type ProviderPreferenceCollectionInput<T> = {
6
+ items: Iterable<T>;
7
+ getProviderPreferences: (item: T) => unknown;
8
+ };
5
9
  type ProviderSelection = {
6
10
  capabilityId: CapabilityId;
7
11
  selectedProviderId: ProviderId;
@@ -36,7 +40,8 @@ type ResolveItemProviderSelectionInput<T> = ProviderCollectionInput<T> & {
36
40
  fallbackProviders?: ProviderPreferenceMap;
37
41
  };
38
42
  declare function collectProvidersByCapability<T>(input: ProviderCollectionInput<T>): ProvidersByCapability;
43
+ declare function collectProviderPreferences<T>(input: ProviderPreferenceCollectionInput<T>): ProviderPreferenceMap;
39
44
  declare function resolveProviderSelection(input: ResolveProviderSelectionInput): ProviderSelectionResolution;
40
45
  declare function resolveItemProviderSelection<T>(input: ResolveItemProviderSelectionInput<T>): ItemProviderSelectionResolution;
41
46
 
42
- export { type CapabilityId, type ItemProviderSelectionResolution, type ProviderId, type ProviderMismatch, type ProviderPreferenceMap, type ProviderSelection, type ProviderSelectionMode, type ProviderSelectionResolution, type ProvidersByCapability, type ResolveItemProviderSelectionInput, type ResolveProviderSelectionInput, collectProvidersByCapability, resolveItemProviderSelection, resolveProviderSelection };
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 };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,10 @@ type CapabilityId = string;
2
2
  type ProviderId = string;
3
3
  type ProviderSelectionMode = 'configured' | 'fallback' | 'first';
4
4
  type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
5
+ type ProviderPreferenceCollectionInput<T> = {
6
+ items: Iterable<T>;
7
+ getProviderPreferences: (item: T) => unknown;
8
+ };
5
9
  type ProviderSelection = {
6
10
  capabilityId: CapabilityId;
7
11
  selectedProviderId: ProviderId;
@@ -36,7 +40,8 @@ type ResolveItemProviderSelectionInput<T> = ProviderCollectionInput<T> & {
36
40
  fallbackProviders?: ProviderPreferenceMap;
37
41
  };
38
42
  declare function collectProvidersByCapability<T>(input: ProviderCollectionInput<T>): ProvidersByCapability;
43
+ declare function collectProviderPreferences<T>(input: ProviderPreferenceCollectionInput<T>): ProviderPreferenceMap;
39
44
  declare function resolveProviderSelection(input: ResolveProviderSelectionInput): ProviderSelectionResolution;
40
45
  declare function resolveItemProviderSelection<T>(input: ResolveItemProviderSelectionInput<T>): ItemProviderSelectionResolution;
41
46
 
42
- export { type CapabilityId, type ItemProviderSelectionResolution, type ProviderId, type ProviderMismatch, type ProviderPreferenceMap, type ProviderSelection, type ProviderSelectionMode, type ProviderSelectionResolution, type ProvidersByCapability, type ResolveItemProviderSelectionInput, type ResolveProviderSelectionInput, collectProvidersByCapability, resolveItemProviderSelection, resolveProviderSelection };
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 };
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  function toSortedUniqueProviderIds(providerIds) {
3
3
  return Array.from(new Set(Array.from(providerIds).filter(Boolean))).sort();
4
4
  }
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
5
8
  function getSelectedProvider(input) {
6
9
  const firstProviderId = input.candidateProviderIds[0];
7
10
  if (!firstProviderId) {
@@ -105,6 +108,22 @@ function collectProvidersByCapability(input) {
105
108
  }
106
109
  return providersByCapability;
107
110
  }
111
+ function collectProviderPreferences(input) {
112
+ let preferences = {};
113
+ for (const item of input.items) {
114
+ const value = input.getProviderPreferences(item);
115
+ if (!isRecord(value)) continue;
116
+ preferences = {
117
+ ...preferences,
118
+ ...Object.fromEntries(
119
+ Object.entries(value).filter(
120
+ (entry) => typeof entry[1] === "string" && entry[1].length > 0
121
+ )
122
+ )
123
+ };
124
+ }
125
+ return preferences;
126
+ }
108
127
  function resolveProviderSelection(input) {
109
128
  const selections = selectProviders(input);
110
129
  const mismatches = findConfiguredProviderMismatches(input);
@@ -128,6 +147,7 @@ function resolveItemProviderSelection(input) {
128
147
  };
129
148
  }
130
149
  export {
150
+ collectProviderPreferences,
131
151
  collectProvidersByCapability,
132
152
  resolveItemProviderSelection,
133
153
  resolveProviderSelection
@@ -0,0 +1,38 @@
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']
@@ -0,0 +1,42 @@
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', ... } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorion-org/provider-selection",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "Framework-free capability provider selection primitives.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,45 +0,0 @@
1
- import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
2
-
3
- type CommandHandler = {
4
- commandId: string;
5
- handlerId: string;
6
- };
7
-
8
- const handlers: CommandHandler[] = [
9
- {
10
- commandId: 'open',
11
- handlerId: 'open-native',
12
- },
13
- {
14
- commandId: 'open',
15
- handlerId: 'open-web',
16
- },
17
- {
18
- commandId: 'share',
19
- handlerId: 'share-link',
20
- },
21
- ];
22
-
23
- const result = resolveItemProviderSelection({
24
- items: handlers,
25
- getCapabilityId: (handler) => handler.commandId,
26
- getProviderId: (handler) => handler.handlerId,
27
- configuredProviders: {
28
- open: 'open-web',
29
- },
30
- });
31
-
32
- console.log(result.providersByCapability);
33
- // { open: ['open-native', 'open-web'], share: ['share-link'] }
34
-
35
- console.log(result.selections);
36
- // {
37
- // open: { selectedProviderId: 'open-web', mode: 'configured' },
38
- // share: { selectedProviderId: 'share-link', mode: 'first' }
39
- // }
40
-
41
- console.log(result.mismatches);
42
- // []
43
-
44
- console.log(result.excludedProviderIds);
45
- // ['open-native']
@@ -1,39 +0,0 @@
1
- import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
2
-
3
- type StorageDriver = {
4
- storageKind: string;
5
- driverId: string;
6
- };
7
-
8
- const drivers: StorageDriver[] = [
9
- {
10
- storageKind: 'blob',
11
- driverId: 's3',
12
- },
13
- {
14
- storageKind: 'blob',
15
- driverId: 'filesystem',
16
- },
17
- {
18
- storageKind: 'queue',
19
- driverId: 'redis-streams',
20
- },
21
- ];
22
-
23
- const result = resolveItemProviderSelection({
24
- items: drivers,
25
- getCapabilityId: (driver) => driver.storageKind,
26
- getProviderId: (driver) => driver.driverId,
27
- fallbackProviders: {
28
- blob: 'filesystem',
29
- },
30
- });
31
-
32
- console.log(result.selections);
33
- // {
34
- // blob: { selectedProviderId: 'filesystem', mode: 'fallback' },
35
- // queue: { selectedProviderId: 'redis-streams', mode: 'first' }
36
- // }
37
-
38
- console.log(result.mismatches);
39
- // []