( ctx: PluginCtx<OnePasswordStore>, timeoutMs: number, preferSdk: boolean | undefined, )
| 280 | // --------------------------------------------------------------------------- |
| 281 | |
| 282 | const makeProvider = ( |
| 283 | ctx: PluginCtx<OnePasswordStore>, |
| 284 | timeoutMs: number, |
| 285 | preferSdk: boolean | undefined, |
| 286 | ): CredentialProvider => ({ |
| 287 | key: PROVIDER_KEY, |
| 288 | writable: false, |
| 289 | |
| 290 | get: (id: ProviderItemId): Effect.Effect<string | null, StorageFailure> => |
| 291 | ctx.storage.getConfig().pipe( |
| 292 | // An undecodable stored config reads as "not configured" here; the |
| 293 | // settings surface reports the decode problem. |
| 294 | Effect.catchTag("OnePasswordError", () => Effect.succeed(null)), |
| 295 | Effect.flatMap((config) => { |
| 296 | if (!config) return Effect.succeed(null as string | null); |
| 297 | |
| 298 | return getServiceFromConfig(config, timeoutMs, preferSdk).pipe( |
| 299 | Effect.flatMap((svc) => resolveConfiguredRef(svc, config, id)), |
| 300 | // Backend unreachability degrades to "no value", matching the other |
| 301 | // providers. Ambiguity does NOT: silently picking a vault (or |
| 302 | // silently failing) hides a real conflict, so it surfaces as a |
| 303 | // typed failure with the full explanation. |
| 304 | Effect.catch(() => Effect.succeed({ kind: "not-found" } as RefResolution)), |
| 305 | Effect.flatMap( |
| 306 | (resolution): Effect.Effect<string | null, StorageError> => |
| 307 | resolution.kind === "ambiguous" |
| 308 | ? Effect.fail( |
| 309 | new StorageError({ |
| 310 | message: ambiguityMessage(id, resolution.matches), |
| 311 | cause: undefined, |
| 312 | }), |
| 313 | ) |
| 314 | : Effect.succeed(resolution.kind === "resolved" ? resolution.value : null), |
| 315 | ), |
| 316 | ); |
| 317 | }), |
| 318 | ), |
| 319 | |
| 320 | list: (): Effect.Effect<readonly ProviderEntry[], StorageFailure> => |
| 321 | ctx.storage.getConfig().pipe( |
| 322 | Effect.flatMap((config) => { |
| 323 | if (!config) return Effect.succeed([] as readonly ProviderEntry[]); |
| 324 | return getServiceFromConfig(config, timeoutMs, preferSdk).pipe( |
| 325 | Effect.flatMap((svc) => |
| 326 | Effect.forEach(config.vaults, (vault) => |
| 327 | svc.listItems(vault.id).pipe( |
| 328 | Effect.map((items) => |
| 329 | items.map( |
| 330 | // Vault-qualified ids: picking an entry permanently |
| 331 | // records which vault it came from, so identically-titled |
| 332 | // items in different vaults can never collide. |
| 333 | (item): ProviderEntry => ({ |
| 334 | id: ProviderItemId.make(`op://${vault.id}/${item.id}`), |
| 335 | name: item.title, |
| 336 | group: vault.name, |
| 337 | }), |
| 338 | ), |
| 339 | ), |
no test coverage detected