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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +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.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # @lorion-org/provider-selection
2
+
3
+ `@lorion-org/provider-selection` is a small framework-free core for selecting one
4
+ provider per capability from multiple candidates.
5
+
6
+ It solves four things:
7
+
8
+ - collect provider candidates by capability
9
+ - optionally collect and resolve in one call
10
+ - pick one provider with configured and fallback preferences
11
+ - report misconfigured provider selections
12
+ - return excluded providers that lost the selection
13
+
14
+ Selection order is always:
15
+
16
+ 1. configured provider
17
+ 2. fallback provider
18
+ 3. first provider in deterministic sort order
19
+
20
+ If a configured provider is set but not present among the candidates, the package
21
+ does not silently fall back. It reports a mismatch and leaves that capability
22
+ unselected.
23
+
24
+ Example files in this repository:
25
+
26
+ - `examples/command-handlers.ts`
27
+ - `examples/storage-drivers.ts`
28
+
29
+ It does not know anything about:
30
+
31
+ - framework runtime config
32
+ - feature manifests
33
+ - plugins
34
+ - filesystems
35
+ - application-specific contract names
36
+
37
+ ## Install
38
+
39
+ ```shell
40
+ pnpm add @lorion-org/provider-selection
41
+ ```
42
+
43
+ ## Example
44
+
45
+ ```ts
46
+ import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
47
+
48
+ const result = resolveItemProviderSelection({
49
+ items: [
50
+ { capability: 'auth', providerId: 'keycloak' },
51
+ { capability: 'auth', providerId: 'auth-local-jwt' },
52
+ { capability: 'mailer', providerId: 'mailer-postmark' },
53
+ ],
54
+ getCapabilityId: (item) => item.capability,
55
+ getProviderId: (item) => item.providerId,
56
+ configuredProviders: {
57
+ auth: 'keycloak',
58
+ },
59
+ fallbackProviders: {
60
+ mailer: 'mailer-postmark',
61
+ },
62
+ });
63
+
64
+ result.selections;
65
+ result.providersByCapability;
66
+ result.mismatches;
67
+ result.excludedProviderIds;
68
+ ```
69
+
70
+ If you already have a `Map<capability, providers>`, use the lower-level
71
+ resolver directly:
72
+
73
+ ```ts
74
+ import { resolveProviderSelection } from '@lorion-org/provider-selection';
75
+
76
+ const result = resolveProviderSelection({
77
+ providersByCapability: new Map([['auth', ['auth-local-jwt', 'keycloak']]]),
78
+ configuredProviders: {
79
+ auth: 'missing-provider',
80
+ },
81
+ });
82
+
83
+ result.selections;
84
+ result.mismatches;
85
+ // => [{ capabilityId: 'auth', configuredProviderId: 'missing-provider' }]
86
+ ```
87
+
88
+ ## Example: command handlers
89
+
90
+ ```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
+ },
104
+ });
105
+ ```
106
+
107
+ ## Example: storage drivers
108
+
109
+ ```ts
110
+ import { resolveItemProviderSelection } from '@lorion-org/provider-selection';
111
+
112
+ 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
+ },
123
+ });
124
+ ```
125
+
126
+ ## API
127
+
128
+ ```ts
129
+ type CapabilityId = string;
130
+ type ProviderId = string;
131
+ type ProviderSelectionMode = 'configured' | 'fallback' | 'first';
132
+ type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
133
+ type ProvidersByCapability = Map<CapabilityId, ProviderId[]>;
134
+
135
+ type ProviderSelection = {
136
+ capabilityId: CapabilityId;
137
+ selectedProviderId: ProviderId;
138
+ candidateProviderIds: ProviderId[];
139
+ mode: ProviderSelectionMode;
140
+ };
141
+
142
+ type ProviderMismatch = {
143
+ capabilityId: CapabilityId;
144
+ configuredProviderId: ProviderId;
145
+ };
146
+
147
+ type ProviderSelectionResolution = {
148
+ selections: Map<CapabilityId, ProviderSelection>;
149
+ mismatches: ProviderMismatch[];
150
+ excludedProviderIds: ProviderId[];
151
+ };
152
+
153
+ type ItemProviderSelectionResolution = ProviderSelectionResolution & {
154
+ providersByCapability: ProvidersByCapability;
155
+ };
156
+ ```
157
+
158
+ The package exposes:
159
+
160
+ - `collectProvidersByCapability()`
161
+ - `resolveItemProviderSelection()`
162
+ - `resolveProviderSelection()`
163
+
164
+ ## Local commands
165
+
166
+ ```shell
167
+ cd packages/provider-selection
168
+ pnpm build
169
+ pnpm test
170
+ pnpm coverage
171
+ pnpm typecheck
172
+ pnpm package:check
173
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ collectProvidersByCapability: () => collectProvidersByCapability,
24
+ resolveItemProviderSelection: () => resolveItemProviderSelection,
25
+ resolveProviderSelection: () => resolveProviderSelection
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ function toSortedUniqueProviderIds(providerIds) {
29
+ return Array.from(new Set(Array.from(providerIds).filter(Boolean))).sort();
30
+ }
31
+ function getSelectedProvider(input) {
32
+ const firstProviderId = input.candidateProviderIds[0];
33
+ if (!firstProviderId) {
34
+ return void 0;
35
+ }
36
+ const configuredProviderId = input.configuredProviders?.[input.capabilityId];
37
+ if (configuredProviderId) {
38
+ if (input.candidateProviderIds.includes(configuredProviderId)) {
39
+ return {
40
+ selectedProviderId: configuredProviderId,
41
+ mode: "configured"
42
+ };
43
+ }
44
+ return void 0;
45
+ }
46
+ const fallbackProviderId = input.fallbackProviders?.[input.capabilityId];
47
+ if (fallbackProviderId && input.candidateProviderIds.includes(fallbackProviderId)) {
48
+ return {
49
+ selectedProviderId: fallbackProviderId,
50
+ mode: "fallback"
51
+ };
52
+ }
53
+ return {
54
+ selectedProviderId: firstProviderId,
55
+ mode: "first"
56
+ };
57
+ }
58
+ function selectProviders(input) {
59
+ const selections = /* @__PURE__ */ new Map();
60
+ for (const [capabilityId, candidateProviderIds] of Array.from(
61
+ input.providersByCapability.entries()
62
+ ).sort(([left], [right]) => left.localeCompare(right))) {
63
+ const normalizedCandidateProviderIds = toSortedUniqueProviderIds(candidateProviderIds);
64
+ const selected = getSelectedProvider({
65
+ capabilityId,
66
+ candidateProviderIds: normalizedCandidateProviderIds,
67
+ ...input.configuredProviders ? { configuredProviders: input.configuredProviders } : {},
68
+ ...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}
69
+ });
70
+ if (!selected) {
71
+ continue;
72
+ }
73
+ selections.set(capabilityId, {
74
+ capabilityId,
75
+ selectedProviderId: selected.selectedProviderId,
76
+ candidateProviderIds: normalizedCandidateProviderIds,
77
+ mode: selected.mode
78
+ });
79
+ }
80
+ return selections;
81
+ }
82
+ function findConfiguredProviderMismatches(input) {
83
+ const mismatches = [];
84
+ for (const [capabilityId, configuredProviderId] of Object.entries(
85
+ input.configuredProviders ?? {}
86
+ ).sort(([left], [right]) => left.localeCompare(right))) {
87
+ if (!configuredProviderId) {
88
+ continue;
89
+ }
90
+ const candidateProviderIds = toSortedUniqueProviderIds(
91
+ input.providersByCapability.get(capabilityId) ?? []
92
+ );
93
+ if (!candidateProviderIds.length) {
94
+ continue;
95
+ }
96
+ if (candidateProviderIds.includes(configuredProviderId)) {
97
+ continue;
98
+ }
99
+ mismatches.push({
100
+ capabilityId,
101
+ configuredProviderId
102
+ });
103
+ }
104
+ return mismatches;
105
+ }
106
+ function getExcludedProviders(selections) {
107
+ const excludedProviderIds = [];
108
+ for (const selection of selections) {
109
+ if (selection.candidateProviderIds.length <= 1) {
110
+ continue;
111
+ }
112
+ for (const candidateProviderId of selection.candidateProviderIds) {
113
+ if (candidateProviderId !== selection.selectedProviderId) {
114
+ excludedProviderIds.push(candidateProviderId);
115
+ }
116
+ }
117
+ }
118
+ return toSortedUniqueProviderIds(excludedProviderIds);
119
+ }
120
+ function collectProvidersByCapability(input) {
121
+ const providersByCapability = /* @__PURE__ */ new Map();
122
+ for (const item of input.items) {
123
+ const capabilityId = input.getCapabilityId(item);
124
+ if (!capabilityId) {
125
+ continue;
126
+ }
127
+ const providerId = input.getProviderId(item);
128
+ const currentProviderIds = providersByCapability.get(capabilityId) ?? [];
129
+ currentProviderIds.push(providerId);
130
+ providersByCapability.set(capabilityId, toSortedUniqueProviderIds(currentProviderIds));
131
+ }
132
+ return providersByCapability;
133
+ }
134
+ function resolveProviderSelection(input) {
135
+ const selections = selectProviders(input);
136
+ const mismatches = findConfiguredProviderMismatches(input);
137
+ const excludedProviderIds = getExcludedProviders(selections.values());
138
+ return {
139
+ selections,
140
+ mismatches,
141
+ excludedProviderIds
142
+ };
143
+ }
144
+ function resolveItemProviderSelection(input) {
145
+ const providersByCapability = collectProvidersByCapability(input);
146
+ const resolution = resolveProviderSelection({
147
+ providersByCapability,
148
+ ...input.configuredProviders ? { configuredProviders: input.configuredProviders } : {},
149
+ ...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}
150
+ });
151
+ return {
152
+ providersByCapability,
153
+ ...resolution
154
+ };
155
+ }
156
+ // Annotate the CommonJS export names for ESM import in node:
157
+ 0 && (module.exports = {
158
+ collectProvidersByCapability,
159
+ resolveItemProviderSelection,
160
+ resolveProviderSelection
161
+ });
@@ -0,0 +1,42 @@
1
+ type CapabilityId = string;
2
+ type ProviderId = string;
3
+ type ProviderSelectionMode = 'configured' | 'fallback' | 'first';
4
+ type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
5
+ type ProviderSelection = {
6
+ capabilityId: CapabilityId;
7
+ selectedProviderId: ProviderId;
8
+ candidateProviderIds: ProviderId[];
9
+ mode: ProviderSelectionMode;
10
+ };
11
+ type ProviderMismatch = {
12
+ capabilityId: CapabilityId;
13
+ configuredProviderId: ProviderId;
14
+ };
15
+ type ProvidersByCapability = Map<CapabilityId, ProviderId[]>;
16
+ type ProviderSelectionResolution = {
17
+ selections: Map<CapabilityId, ProviderSelection>;
18
+ mismatches: ProviderMismatch[];
19
+ excludedProviderIds: ProviderId[];
20
+ };
21
+ type ItemProviderSelectionResolution = ProviderSelectionResolution & {
22
+ providersByCapability: ProvidersByCapability;
23
+ };
24
+ type ProviderCollectionInput<T> = {
25
+ items: Iterable<T>;
26
+ getCapabilityId: (item: T) => CapabilityId | undefined;
27
+ getProviderId: (item: T) => ProviderId;
28
+ };
29
+ type ResolveProviderSelectionInput = {
30
+ providersByCapability: ProvidersByCapability;
31
+ configuredProviders?: ProviderPreferenceMap;
32
+ fallbackProviders?: ProviderPreferenceMap;
33
+ };
34
+ type ResolveItemProviderSelectionInput<T> = ProviderCollectionInput<T> & {
35
+ configuredProviders?: ProviderPreferenceMap;
36
+ fallbackProviders?: ProviderPreferenceMap;
37
+ };
38
+ declare function collectProvidersByCapability<T>(input: ProviderCollectionInput<T>): ProvidersByCapability;
39
+ declare function resolveProviderSelection(input: ResolveProviderSelectionInput): ProviderSelectionResolution;
40
+ declare function resolveItemProviderSelection<T>(input: ResolveItemProviderSelectionInput<T>): ItemProviderSelectionResolution;
41
+
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 };
@@ -0,0 +1,42 @@
1
+ type CapabilityId = string;
2
+ type ProviderId = string;
3
+ type ProviderSelectionMode = 'configured' | 'fallback' | 'first';
4
+ type ProviderPreferenceMap = Partial<Record<CapabilityId, ProviderId>>;
5
+ type ProviderSelection = {
6
+ capabilityId: CapabilityId;
7
+ selectedProviderId: ProviderId;
8
+ candidateProviderIds: ProviderId[];
9
+ mode: ProviderSelectionMode;
10
+ };
11
+ type ProviderMismatch = {
12
+ capabilityId: CapabilityId;
13
+ configuredProviderId: ProviderId;
14
+ };
15
+ type ProvidersByCapability = Map<CapabilityId, ProviderId[]>;
16
+ type ProviderSelectionResolution = {
17
+ selections: Map<CapabilityId, ProviderSelection>;
18
+ mismatches: ProviderMismatch[];
19
+ excludedProviderIds: ProviderId[];
20
+ };
21
+ type ItemProviderSelectionResolution = ProviderSelectionResolution & {
22
+ providersByCapability: ProvidersByCapability;
23
+ };
24
+ type ProviderCollectionInput<T> = {
25
+ items: Iterable<T>;
26
+ getCapabilityId: (item: T) => CapabilityId | undefined;
27
+ getProviderId: (item: T) => ProviderId;
28
+ };
29
+ type ResolveProviderSelectionInput = {
30
+ providersByCapability: ProvidersByCapability;
31
+ configuredProviders?: ProviderPreferenceMap;
32
+ fallbackProviders?: ProviderPreferenceMap;
33
+ };
34
+ type ResolveItemProviderSelectionInput<T> = ProviderCollectionInput<T> & {
35
+ configuredProviders?: ProviderPreferenceMap;
36
+ fallbackProviders?: ProviderPreferenceMap;
37
+ };
38
+ declare function collectProvidersByCapability<T>(input: ProviderCollectionInput<T>): ProvidersByCapability;
39
+ declare function resolveProviderSelection(input: ResolveProviderSelectionInput): ProviderSelectionResolution;
40
+ declare function resolveItemProviderSelection<T>(input: ResolveItemProviderSelectionInput<T>): ItemProviderSelectionResolution;
41
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,134 @@
1
+ // src/index.ts
2
+ function toSortedUniqueProviderIds(providerIds) {
3
+ return Array.from(new Set(Array.from(providerIds).filter(Boolean))).sort();
4
+ }
5
+ function getSelectedProvider(input) {
6
+ const firstProviderId = input.candidateProviderIds[0];
7
+ if (!firstProviderId) {
8
+ return void 0;
9
+ }
10
+ const configuredProviderId = input.configuredProviders?.[input.capabilityId];
11
+ if (configuredProviderId) {
12
+ if (input.candidateProviderIds.includes(configuredProviderId)) {
13
+ return {
14
+ selectedProviderId: configuredProviderId,
15
+ mode: "configured"
16
+ };
17
+ }
18
+ return void 0;
19
+ }
20
+ const fallbackProviderId = input.fallbackProviders?.[input.capabilityId];
21
+ if (fallbackProviderId && input.candidateProviderIds.includes(fallbackProviderId)) {
22
+ return {
23
+ selectedProviderId: fallbackProviderId,
24
+ mode: "fallback"
25
+ };
26
+ }
27
+ return {
28
+ selectedProviderId: firstProviderId,
29
+ mode: "first"
30
+ };
31
+ }
32
+ function selectProviders(input) {
33
+ const selections = /* @__PURE__ */ new Map();
34
+ for (const [capabilityId, candidateProviderIds] of Array.from(
35
+ input.providersByCapability.entries()
36
+ ).sort(([left], [right]) => left.localeCompare(right))) {
37
+ const normalizedCandidateProviderIds = toSortedUniqueProviderIds(candidateProviderIds);
38
+ const selected = getSelectedProvider({
39
+ capabilityId,
40
+ candidateProviderIds: normalizedCandidateProviderIds,
41
+ ...input.configuredProviders ? { configuredProviders: input.configuredProviders } : {},
42
+ ...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}
43
+ });
44
+ if (!selected) {
45
+ continue;
46
+ }
47
+ selections.set(capabilityId, {
48
+ capabilityId,
49
+ selectedProviderId: selected.selectedProviderId,
50
+ candidateProviderIds: normalizedCandidateProviderIds,
51
+ mode: selected.mode
52
+ });
53
+ }
54
+ return selections;
55
+ }
56
+ function findConfiguredProviderMismatches(input) {
57
+ const mismatches = [];
58
+ for (const [capabilityId, configuredProviderId] of Object.entries(
59
+ input.configuredProviders ?? {}
60
+ ).sort(([left], [right]) => left.localeCompare(right))) {
61
+ if (!configuredProviderId) {
62
+ continue;
63
+ }
64
+ const candidateProviderIds = toSortedUniqueProviderIds(
65
+ input.providersByCapability.get(capabilityId) ?? []
66
+ );
67
+ if (!candidateProviderIds.length) {
68
+ continue;
69
+ }
70
+ if (candidateProviderIds.includes(configuredProviderId)) {
71
+ continue;
72
+ }
73
+ mismatches.push({
74
+ capabilityId,
75
+ configuredProviderId
76
+ });
77
+ }
78
+ return mismatches;
79
+ }
80
+ function getExcludedProviders(selections) {
81
+ const excludedProviderIds = [];
82
+ for (const selection of selections) {
83
+ if (selection.candidateProviderIds.length <= 1) {
84
+ continue;
85
+ }
86
+ for (const candidateProviderId of selection.candidateProviderIds) {
87
+ if (candidateProviderId !== selection.selectedProviderId) {
88
+ excludedProviderIds.push(candidateProviderId);
89
+ }
90
+ }
91
+ }
92
+ return toSortedUniqueProviderIds(excludedProviderIds);
93
+ }
94
+ function collectProvidersByCapability(input) {
95
+ const providersByCapability = /* @__PURE__ */ new Map();
96
+ for (const item of input.items) {
97
+ const capabilityId = input.getCapabilityId(item);
98
+ if (!capabilityId) {
99
+ continue;
100
+ }
101
+ const providerId = input.getProviderId(item);
102
+ const currentProviderIds = providersByCapability.get(capabilityId) ?? [];
103
+ currentProviderIds.push(providerId);
104
+ providersByCapability.set(capabilityId, toSortedUniqueProviderIds(currentProviderIds));
105
+ }
106
+ return providersByCapability;
107
+ }
108
+ function resolveProviderSelection(input) {
109
+ const selections = selectProviders(input);
110
+ const mismatches = findConfiguredProviderMismatches(input);
111
+ const excludedProviderIds = getExcludedProviders(selections.values());
112
+ return {
113
+ selections,
114
+ mismatches,
115
+ excludedProviderIds
116
+ };
117
+ }
118
+ function resolveItemProviderSelection(input) {
119
+ const providersByCapability = collectProvidersByCapability(input);
120
+ const resolution = resolveProviderSelection({
121
+ providersByCapability,
122
+ ...input.configuredProviders ? { configuredProviders: input.configuredProviders } : {},
123
+ ...input.fallbackProviders ? { fallbackProviders: input.fallbackProviders } : {}
124
+ });
125
+ return {
126
+ providersByCapability,
127
+ ...resolution
128
+ };
129
+ }
130
+ export {
131
+ collectProvidersByCapability,
132
+ resolveItemProviderSelection,
133
+ resolveProviderSelection
134
+ };
@@ -0,0 +1,45 @@
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']
@@ -0,0 +1,39 @@
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
+ // []
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@lorion-org/provider-selection",
3
+ "version": "1.0.0-beta.0",
4
+ "description": "Framework-free capability provider selection primitives.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/lorion-org/lorion.git",
9
+ "directory": "packages/provider-selection"
10
+ },
11
+ "homepage": "https://github.com/lorion-org/lorion/tree/main/packages/provider-selection#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/lorion-org/lorion/issues"
14
+ },
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "main": "./dist/index.cjs",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "import": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "require": {
30
+ "types": "./dist/index.d.cts",
31
+ "default": "./dist/index.cjs"
32
+ }
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "examples",
38
+ "LICENSE"
39
+ ],
40
+ "keywords": [
41
+ "provider",
42
+ "selection",
43
+ "capability",
44
+ "typescript"
45
+ ],
46
+ "engines": {
47
+ "node": "^20.19.0 || >=22.12.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsup src/index.ts --format esm,cjs --dts",
51
+ "clean": "rimraf coverage dist tsconfig.tsbuildinfo",
52
+ "lint": "eslint src --ext .ts",
53
+ "test": "vitest run --root ../.. --config vitest.config.mts packages/provider-selection/src/index.spec.ts",
54
+ "coverage": "node -e \"require('node:fs').mkdirSync('../../coverage/.tmp', { recursive: true })\" && vitest run --coverage --coverage.include=packages/provider-selection/src/**/*.ts --root ../.. --config vitest.config.mts packages/provider-selection/src/index.spec.ts",
55
+ "typecheck": "tsc -p tsconfig.json --noEmit",
56
+ "package:check": "pnpm build && pnpm pack --dry-run && publint"
57
+ }
58
+ }